Как стать Delphi-программистом за час «для самых маленьких»11.12.2023 08:00
// указываем имя нашей библиотеки
unit Unit3;
// способ компиляции файла
{$mode ObjFPC}{$H+}
// указатель того, что наш тип данных может быть интерфейсом
interface
// подключим библиотеки
uses
// подключаем нашу другую библиотеку - Unit2, нам понадобится
// объект, который мы в ней создавали
Unit2,
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Grids,
ExtCtrls;
// опишем заголовок нашего типа данных
//
// то, что мы собираем в итоге, можно назвать "условным модулем" нашей
// программы, в обычной программе таких модулей много, для удобной
// реализации, и поиска вероятных ошибок, мы разбиваем наш модуль
// на составные "классы" (наши типы данных), для каждого из них отдельно
// описываем логику, запускаем, проверяем, он работает, пишем дальше,
// после чего на базе этого "класса" создаем дочерний, который сохраняет
// и копирует весь функционал и логику родителя, а также дополняет ее,
// это основная из фишек ооп, именно данный метод (которым мы сейчас
// описываем программу называется классически (из концепции ооп),
// который построен в формате зависимостей "родительский к дочернему",
//
// еще немного о "модификаторах доступа" к родительскому содержимому,
// модификаторы доступа, это ключевые слова public, private, protected,
// и тд, их задача изолировать ненужные части родительского объекта,
// в дочернем в рамках всего кода программы (для масштабных программ,
// чтобы случайно, или если забудем, не вмешаться в уже рабочий компонент
// (класс) и не сломать его, или то, что от него зависимо), мы будем
// проектировать наш "модуль" по классической логической зависимости,
// со свободным доступом ко всем элементам и родителям модуля, и для
// каждого дочернего элемента также будем оставлять общедоступным (public)
// все, и только в последнем классе нашего модуля (который уже соберет из
// всей этой матрешки классов) готовый компонент закроем все что не
// должно быть видно, и является "системным" для нашего компонента
//
// такой подход позволяет гибко (и в любой момент изменять модуль),
// это классический подход ооп (из концепции), но он не позволяет
// использовать какие-либо части нашего модуля в других компонентах,
// кроме того, для разных языков программирования существуют разные
// базовые концепции разработки, к примеру в с++ это просто условность,
// в джаве и си шарп подход иной (у веб вообще нет)
//
// точно также как разные модификаторы доступа "по умолчанию", в паскале,
// если не написать не одного модификатора доступа (внутри заголовка класса),
// редактор кода сам добавит модификатор перед компиляцией (модификатор
// PUBLISHED), учитываете это, и всегда для каждого свойства четко
// определяйте модификатор доступа, иначе могут быть глупые логические
// ошибки доступа (внутри сгенерированного кода редактором в том числе,
// так как он генерирует все объекты под одним модификатором для конкретной
// группы объектов
//
// модификатор PUBLISHED позволяет работать в рунтайме, также он может
// в нарушение логики построения компонента открывать доступ "вне очереди"
// построения (иначе говоря, содержимое доступно везде и всегда),
// модификатор PUBLIC стандартная публичная секция, все что идет после
// ключевого слова PUBLIC доступно из любой части нашей программы (по
// обращению, конечно)
// модификатор PROTECTED открывает доступ содержимого родителя только
// для дочерних классов (для базовой концепции разработки ооп), вне
// дочерних классов (в рамках кода программы), увидеть (а соответственно
// и изменить невозможно)
// модификатор PRIVATE (или специальный), добавлен для обработки исключений
// из стандартной концепции ооп, он закрывает какие-либо свойства, функции,
// детали текущего класса для всех, кроме себя, в реальной разработке
// не требуется, PUBLIC (PUBLISHED) и PROTECTED хватает с головой, но для
// каких-то особых "изобретений" может быть полезен, как и оставшиеся -
// STRICT PROTECTED, и STRICT PRIVATE для системных нужд (к PUBLISHIED)
//
// опишем этот класс, добавив в наш модуль область для рисования (из COM
// TImage), а также опишем парсер, чтобы из текста, который написан
// в редакторе извлекать какие-то данные, чтобы использовать их
// для рисования на объекте Canvas (у заготовки TImage), объект
// Canvas универсальный и есть во всех языках программирования
// (по крайней мере каноннистических), после ключевого слова class,
// в круглых () скобках указываем тип данных из которого хотим "закачать"
// все в наш тип данных (у нас это TextFileLoader)
type TComDrawer = class (TextFileLoader)
// добавим объект TImage (область для рисования)
PaintField:TImage;
// создаем переменную типа TRect (четыре числовых
// переменных в одной), будем хранить в ней размеры фигуры
FigureSize:TRect;
// создаем текстовую переменную, будем хранить в ней
// ключевое слово, в соответствии с которым будем рисовать
// ту, или иную фигуру, тип string оригинальный текстовый
// типе данных TCaption берет его и расширяет (является дочерним)
Figure:string;
// переопределим конструктор, наличие одноименной функции
// родительского класса (с идентичным набором аргументов
// функции) позволяет переопределить логику работы этой
// функции в нашем дочернем классе при помощи ключевого
// слова REINTRODUCE, при этом в новой реализации можно
// вызвать выполнение оригинальной логики, при помощи ключевого
// слова INHERITED
constructor New(Owner:TForm); REINTRODUCE;
// добавим "альтернативный" конструктор для нашего класса,
// он будет принимать в себя размер области для рисования,
// ключевое слово OVERLOAD говорит о том, что этот метод
// (конструктор класса, New) "перегружаем", то есть если
// во время вызова указать определенный набор аргументов
// функции (в круглых скобках после имени функции), с
// определенной последовательностью, то будет вызван вариант,
// который соответствует по набору аргументов, "перегрузок"
// может быть сколько угодно
PRIVATE constructor New(Owner:TForm;
PaintWidth:integer;
PaintHeight:integer
); OVERLOAD;
// опишем "альтернативный" конструктор, если функция имеет
// "модификатор" VIRTUAL - это значит что функция может быть
// заменена на другую функцию с таким же названием (в реализации
// логики)
PRIVATE constructor Create(Owner:TForm); VIRTUAL;
// опишем еще один конструктор, теперь с модификатором ABSTRACT,
// он позволяет выполнить "отложенное" описание в логике дочерних
// классов (то есть это заготовка для функции, просто объявленный
// заголовок функции в заголовке класса, который предполагает свою
// реализацию в других (дочерних) классах (в текущем - нет)
PRIVATE constructor Create(Owner:TForm; Size:TPoint); VIRTUAL; ABSTRACT;
// опишем заголовок функции для парсинга (парсинг это разбор
// строки (посимвольно) и поиск каких-либо фраз, в зависимости
// от этих фраз извлечение каких-либо данных из строки текста
// функция будет принимать в себя два текста, один будет началом
// ключа, второй концом ключа, возвращать функция будет возвращать
// извлеченные данные
PUBLIC function ParseText(KeyStart:TCaption; KeyEnd:TCaption; Text:TCaption):TCaption;
// создадим функцию, которую ассоциируем с событием (стандартным)
// нажатия клавиш, у редактора текста (для объявления собственных событий
// в класс надо добавить переменную типа TNotifyEvet, после чего свойство
// для этой переменной, и функцию, которая будет проверять наличие
// привязанной (к свойству) функции, и запускать, для того чтобы создать
// свою функцию для стандартного события COM объекта, она должна
// соответствовать универсальным параметрам (аргументам, которые функция
// принимает в себя во время вызова), тут это код клавиши, которая была
// нажата (Key), состояние нажатия (нажата, удержана, отпускается),
// параметр Shift, первый параметр - это объект, который вызвал эту функцию
PUBLIC procedure Ext_TextEditorKeyDown(
Sender: TObject;
var Key: Word;
Shift: TShiftState
);
// опишем заголовок функции для отрисовки чего-либо на области рисования
PUBLIC function DrawOnPaintField:boolean;
end;
implementation
constructor TComDrawer.New(Owner:TForm);
// объявим вспомогательную переменную
// типа число, локальные (к какому-либо блочку кода)
// переменные автоматически уничтожаются по завершении
// этого блочка кода (блочка, в рамках которого они были
// объявлены)
var a:integer;
begin
// сначала вызовем выполнение оригинального содержимого
// функции New из класса TextFileLoader, в качестве аргументов
// передаем значение, получаемое из аргумента этой функции
INHERITED New(Owner);
// теперь переопределим размеры редактора текста и разместим
// область для рисования в пределах этой территории
//
// во вспомогательную переменную поместим текущую ширину
// редактора текста, так как это дочерний класс, все из
// класса родителя доступно для управления отсюда, ключевое
// слово Self позволяет обратиться к самому себе (классу),
// так будто класс - это готовая переменная нашего типа данных
a:=Self.TextViewer.Width;
// изменим ширину объекта из расчета 100 пикселей отступа
// между областью рисования и редактором, для этого размер
// редактора делим на 2 (div - это деление без остатка), и
// вычитаем 50
Self.TextViewer.Width:= (a div 2) - 50;
// создадим из заготовки TImage новый экземпляр объекта,
// и ассоциируем его с переменной PaintField
PaintField:=TImage.Create(Owner);
// зададим, где отрисовывать нашу область рисования, указав
// родителя объекта (не связанно с родителями в классе),
// это свойство формы
PaintField.Parent:=Owner;
// зададим высоту области рисования
PaintField.Height:=Owner.Height - 200;
// зададим ширину, исходя из содержимого переменной а
PaintField.Width:=(a div 2) +50;
// перепозиционируем область рисования (по умолчанию
// все объекты создаются в координатах 0, 0, то есть
// в левом верхнем углу окна программы
PaintField.Top:=100;
// вызываем все тот же кусочек, которым вычитывали новые размеры
// для редактора кода, только теперь не вычитаем, а прибавляем
// плюс 50 пикселей, вместе с отступом редактора получаем 100
PaintField.Left:=(a div 2) + 100;
// изменяем цвет фона области рисования на черный
PaintField.Canvas.Brush.Color:=clBlack;
// заполняем всю область черным цветом
PaintField.Canvas.FillRect(0,0, PaintField.Width, PaintField.Height);
// привяжем к редактору текста процедуру, которая будет
// обрабатывать события нажатия клавиш
TextViewer.OnKeyDown:=@Ext_TextEditorKeyDown;
// определим стандартные размеры фигуры, в случае если получим
// команду рисования, без размеров и позиции, по умолчанию координаты
// левый верхний край, 1\3 размера области рисования (позиция имеет
// отступ от края области TImage 2 пикселя)
FigureSize.Left:=2;
FigureSize.Top:=2;
FigureSize.Right:=PaintField.Width div 3;
FigureSize.Bottom:=PaintField.Height div 3;
// определим ключевое слово для определения фигуры "по умолчанию"
Figure:='PARAM_CIRCLE';
end;
constructor TComDrawer.New(Owner:TForm;
PaintWidth:integer;
PaintHeight:integer
);
begin
INHERITED New(Owner);
end;
constructor TComDrawer.Create(Owner:TForm);
begin
INHERITED New(Owner);
end;
// опишем функцию для парсинга (прасинг это процесс разбора строки, и
// извлечения куска текста из строки
function TComDrawer.ParseText(KeyStart:TCaption; KeyEnd:TCaption; Text:TCaption):TCaption;
// объявляем переменные (типа число)
var a, b:integer;
c:TCaption;
// объявляем указатель на строку, к которому можно прыгнуть
// прыгнуть вне последовательного выполнения (построчной)
// логики нашей функции
label ASMSTR;
begin
// наполняем вспомогательные переменные
a:=0;
// эта переменная буде содержать количество
// символов в передаваемом тексте (в переменную
// Text), там, где она будет вызвана, так как
// 1 в программировании это 0, вычитаем из
// количества символов 1, чтобы не получить
// ошибку (когда мы попробуем обратится к символу
// по индексу, а там только ошибка доступа к памяти),
// функция Length возвращает абстрактное количество
// чего-либо (высокоуровневое программирование), поэтому
// ее можно применять к любым объекта в коде
b:=Length(Text);
// объявляем внутри функции точку, к которой можно прыгнуть
// при помощи команды GOTO
ASMSTR:
// передаем команду компилятору, указываем что команды
// ассемблера надо интерпретировать при помощи концепции
// логики INTEL (ассемблер x32, по сути, толку от него нет,
// так как команды из набора ассемблера х64 он не поддерживает),
//
{$ASMMODE INTEL}
// ассемблер - это язык прямых логических команд для АЛУ (в процессоре),
// в него, по сути, все компиляторы (на всех языках) компилируют файлы
// с кодом (как этот файл), в двух слова в ассемблере есть несколько
// фиксированных переменных разного размера (хранилища для значений,
// или супер кэш), и около 500+ логических, и арифметических команд,
// над значениями, которые ассоциированы с этими (фиксированными)
// переменными, по сути, для знания ассемблера достаточно несколько
// команд (MOV - переместить данные из eax в ebx, ADD - сложить eax и ebx,
// внутрь eax, SUB - вычесть из eax, ebx, оставить значение в eax, DIV -
// разедлить eax на ebx, результат в eax, MUL - умножить eax на ebx,
// результат в eax, CMP - сравнить eax и ebx, если не равны во встроенной
// (фиксированной, переменной, другой ZF), INC увеличить eax на 1,
// DEC - уменьшить eax на 1, JMP - врыгнуть (тут должно быть "прыгнуть", но мне так понравилась ошибка что я решил ее оставить)к какому-либо блочку кода
// (на подобие лейбла "label ASMSTR;" в нашей функции, хотя по сути
// паскаль очень много чего берет из ассемблера, например концепцию
// процедур, некоторые команды и вовсе дублируются, как и ключевые слова),
// PUSH - отправить значение eax в свободную ячейку оперативной памяти
// (на усмотрение процесса), POP достать данные из оперативной памяти,
// этих команд хватит с головой чтобы делать практически все, единственная
// проблема это eax, ebx, ecx, edx и тд (или фиксированные переменные),
// дело в том, что таких переменных довольно много, формально с ними можно
// делать что угодно, но на практике все эти переменные используются
// в каких-либо операциях (к примеру, переменная ZF получает в себя 1,
// если команда CMP сравнила два значения, и они оказались равны, и тд),
// но если познакомится с документацией по наборам команд логики процессора
// то проблем не будет
//
// напомню, что программы на чистом ассемблере (то есть
// имеющие не естественные для компилятора сигнатуры кода) автоматически
// объявляются вирусами (не у всех, но многих антивирусов), также если
// программа попытается получить "неавторизированный" доступ к памяти (то
// есть если к переменной обращается программа, которая не создавала эту
// переменную), она рискует получить значение из песочницы антивируса,
// либо windows просто заблокирует эту операцию (исключения составляют
// перегружаемые программы (OLE программы, либо программы, в которых
// предусмотрена работа с OLE объектами), иначе говоря, программы, в которых
// реализовано предоставление доступа к своей памяти, для других программ
// (к примеру, excel, в котором есть полный функционал для работы с этой
// программой без графического интерфейса, которая может быть запущенна
// из другой программы), но это ограничение легко обходится изменением
// идентификационных данных программы (в системе), на идентификационные
// данные программы, к чьей памяти нужно обратится (то есть кряк через
// хук на процесс программы), но такие действия легко обнаруживаются
// и системой, и антивирусом), в общем ассемблер надо использовать с умом
//
// уведомляем компилятор о том, что этот блочок кода не нужно компилировать,
// так как это уже команды ассемблера
asm
;{вызываем команду MOV, в качестве параметров}
;{передаем супер кэш eax и нашу переменную а,}
;{то есть копируем значение переменной а в eax}
MOV eax, a;
;{увеличиваем значение eax на 1}
INC eax
;{вертаем в зад (в переменную а)}
MOV a, eax
;{конец блочка кода ассемблера}
end;
// в данном примере мы организуем цикл без ключевого слова while,
// for, repeat, мы просто задаем условие, если значение переменной, а
// меньше значения переменной б, выполняем код условия, такая конструкция
// все равно обращается к оперативной памяти (мы ворочим значение в а),
// поэтому для того, чтобы реально использовать преимущества супер кэша,
// нужно ворочить счетчик (переменную а), лимит (переменную б), в супер
// кэш, и еще там же выполнять проверку условия
//
// есть несколько типов условий < - меньше, > - больше, = - соответствует,
// <> - больше, меньше (не соответствует), и специальные (для отсчетов,
// где 0 это 1, но при этом второе значение сравнения изначально 1),
// <= - меньше, или ровно, >= - больше, или равно, это тот случай
if (a <= b) then
begin
// внутри первого условия задаем второе, если первый символ из
// значение переменной, передаваемой во время вызова функции
// не соответствует символу, который находится в тексте, который
// мы тоже передаем при вызове функции (символ для сравнения с
// KeyStart берем из индекса буквы в тексте, а индекс берем из
// переменной а, которая, после каждого выполнения увеличивается
// на 1)
if (KeyStart[1] <> Text[a]) then
begin
// прерываем построчное выполнение логики нашей функции,
// и прыгаем к ключевому слову ASMSTR, чтобы продолжить
// построчное выполнение кода оттуда
GOTO ASMSTR;
end
// описываем блочок альтернативы условия (то есть если первый
// символ из KeyStart соответствует какому-либо символу из Text)
ELSE
begin
// если выполняется альтернативный блок, значит мы сейчас находимся
// в месте, где начинается "ограничитель" значения, которые нам нужно,
// теперь мы должны передвинутся на 1 символ вперед, и собирать весь
// текст во вспомогательную переменную, до тех пор, пока не встретим
// следующий ограничитель, который будет играть роль "закрывающего
// ограничителя"
//
// в переменную с помещаем ничего
c:='';
// перемещаемся на следующий символ
a:=a+1;
// запускаем цикл, до тех пор, пока а меньше б
// выполняем код цикла снова
while a <= b do
begin
// задаем условие, если первый символ
// из переменной KeyEnd соответствует
// символу (из Text) под индексом (в тексте),
// на место которого помещаем содержимое
// переменной а (счетчик), выполняем код блочка
// внутри условия
if KeyEnd[1] = Text[a] then
begin
// прерываем все циклы,
// нарушением логики, поместив
// в а значение из б
a:=b;
// возвращаем результат выполнения
// функции, присваиваем скрытой переменной
// result (она есть у каждой функции),
// содержимое из с
result:=c;
end ELSE
begin
// в содержимое переменной с приплюсовываем
// ее собственное содержимое, и символ из Text,
// с индексом буквы из переменной а, но при
// условии что символ не соответствует символу
// "закрывающего ключа" для парсинга текста
// (конструкция ELSE)
c:=c+Text[a];
end;
// после каждого прохода цикла увеличиваем
// значение переменной, а на 1 (при помощи
// специальной функции из библиотек)
inc(a);
end;
// в переменную а помещаем значение переменной б
a:=b;
end;
end;
end;
// опишем функцию (процедуру) для события нажатия по редактору текста,
// можно использовать библиотеки ShellAPI чтобы обрабатывать события
// нажатий вне стандартных событий, коды клавиш доступны в интернете,
// для получения координат курсора используется объект Mouse.CursorPos.Y,
// или X, также можно использовать функции из ShellAPI (GetCursorPos)
procedure TComDrawer.Ext_TextEditorKeyDown(
Sender: TObject;
var Key: Word;
Shift: TShiftState
);
// создадим вспомогательные переменные
var text1, text2:string;
a,b:integer;
begin
// создаем условие, если код нажатой клавиши соответствует
// коду 1B, который при помощи символа $ компилятор трансформирует
// в х16 формат значения, выполняем действия условия (1B - код
// клавиши Esc)
if (Key = $1B) then
begin
// наполняем переменные для цикла
a:=0;
// если каретка в редакторе дальше строки с индексом 0
// на всякий случай обработаем "неполное" выполнение
// команд в текстовом редакторе (не баг а фича)
a:=TextViewer.CaretPos.Y;
// узнаем общее количество строк в текстовом редакторе
b:=TextViewer.Lines.Count;
// создаем цикл с условие, пока а меньше б выполняем
// действия цикла
while a < b do
begin
// реализуем разновидность "парсинга" при помощи
// замены текста
//
// переменной text1 присваиваем (копируем) содержимое
// строки текста с индексом, который берем из позиции
text1:=TextViewer.Lines.Strings[a];
// с переменной text2 ассоциируем результат выполнения
// функции для замены значений в тексте (встроенная
// функция из библиотек), в качестве параметров передаем
// текст для поиска, значение для замены (у меня ничего),
// параметры условий замены (игнорировать РЕГИСТР, заменить
// все найденные результаты), ну и текст, в котором производить
// замену
text2:=StringReplace(text1, 'размер', '', [rfReplaceAll, rfIgnoreCase]);
// создаем условие, если содержимое text1 не соответствует
// содержимому text2 (в которое попадает текст, после замены в
// тексте всех ключевых слов), выполняем код условия
if text1 <> text2 then
begin
text2:=ParseText('"','"',text1);
// так как мы знаем, что ключевое слово - это "размер"
// изменяем у переменной с размерами размер, задаем
// размер из содержимого, которое мы распарсили в переменную
// text2, при этом с помощью функции из библиотеке
// конвертируем текст в число, это ширина
FigureSize.Width:=SysUtils.StrToInt(text2);
FigureSize.Height:=(SysUtils.StrToInt(text2) div 4) * 3;
end;
// аналогичные действия проделаем для всех команд
text1:=TextViewer.Lines.Strings[a];
text2:=StringReplace(text1, 'позиция', '', [rfReplaceAll, rfIgnoreCase]);
if text1 <> text2 then
begin
// вызываем нашу функцию для парсинга, я буду использовать
// как метки для "распарсивания" текста символ ("), так как
// переменная text2 больше не нужна (но еще существует, так как
// конец функции не достигнут), использую text2 как хранилище
text2:=ParseText('"','"',text1);
// для расположения используем два свойства
// (вложенных в переменную FigureSize) из нашей переменной
// с размером
FigureSize.Left:=SysUtils.StrToInt(text2);
// высота будет 3\4 от ширины
FigureSize.Top:=(SysUtils.StrToInt(text2) div 4) * 3;
end;
text1:=TextViewer.Lines.Strings[a];
text2:=StringReplace(text1, 'круг', '', [rfReplaceAll, rfIgnoreCase]);
if text1 <> text2 then
begin
// для определения фигуры используем другую переменную,
// я выбрал ключевое слово PARAM_CIRCLE
Figure:='PARAM_CIRCLE';
// вызываем функцию (которая еще не описана), мы можем
// получить к ней доступ, за счет отложенного описания
// (потому что в начале класса мы объявили заголовок этой
// функции, то есть избавлены от логической ошибки вызова
// функции раньше, чем она будет создана, PUBLISHED может
// нарушать этот порядок)
DrawOnPaintField;
end;
text1:=TextViewer.Lines.Strings[a];
text2:=StringReplace(text1, 'квадрат', '', [rfReplaceAll, rfIgnoreCase]);
if text1 <> text2 then
begin
Figure:='PARAM_RECTANGLE';
DrawOnPaintField;
end;
text1:=TextViewer.Lines.Strings[a];
text2:=StringReplace(text1, 'текст', '', [rfReplaceAll, rfIgnoreCase]);
if text1 <> text2 then
begin
Figure:='PARAM_TEXT';
DrawOnPaintField;
end;
// увеличиваем счетчик на 1 после каждого прохода
inc(a);
end;
end;
end;
// опишем последнюю функцию (для отрисовки)
function TComDrawer.DrawOnPaintField:boolean;
begin
// меняем цвет рисоваемой линии на красный
PaintField.Canvas.Pen.Color:=clRed;
// размер линии задаем 2 пикселя
PaintField.Canvas.Pen.Width:=2;
// рисовать будем исходя из содержимого переменной Figure
if (Figure = 'PARAM_CIRCLE') then
begin
// так как в переменной PaintField уже есть размеры и позиция,
// буду брать размеры и координаты из нее
PaintField.Canvas.Ellipse(
FigureSize.Left,
FigureSize.Top,
FigureSize.Width,
FigureSize.Height
);
end;
// аналогично для квадрата
if (Figure = 'PARAM_RECTANGLE') then
begin
// так как в переменной PaintField уже есть размеры и позиция,
// буду брать размеры и координаты из нее (в качестве параметров для
// стандартной функции отрисовки квадрата)
PaintField.Canvas.Rectangle(
FigureSize.Left,
FigureSize.Top,
FigureSize.Width,
FigureSize.Height
);
end;
// и текста
if (Figure = 'PARAM_TEXT') then
begin
// для отрисовки текста возьмем только расположение,
// текст фиксированный
// цвет текста задаем зеленый
PaintField.Canvas.Font.Color:=clGreen;
// размер 12em
PaintField.Canvas.Font.Size:=12;
PaintField.Canvas.TextOut(
FigureSize.Left,
FigureSize.Top,
'ЭТО ТЕКСТ АААААААААААА'
);
end;
end;
// конец описания класса (следовало бы создать еще один файл,
// в котором закрыть класс (все в наших классах имеет модификатор
// PUBLISHED, потому что я не указывал модификаторы, и был использован
// модификатор "по умолчанию"), но так как все публичное, для этого
// надо создать класс "обертку", внутри которого создать переменную
// (в закрытой секции) типа наш тип данных, сам же объект не делать
// наследующим, и просто в конструкторе создать объект, но я устал, поэтому так
end.
© Habrahabr.ru