Как стать Delphi-программистом за час «для самых маленьких»

931145e23c4d1af9de4a332cebbaa239.png
// указываем имя нашей библиотеки
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