Принято считать, что программирование это сложно, но это миф (все проще чем кажется), и все что нужно чтобы стать программистом это немного упорства и изобретательности.
В этой статье мы получим все базовые навыки, которые нужны Delphi программисту (включая базовые знания RunTime, работу с Com‑объектами и Canvas, вводом\выводом, файлами, парсингом, ооп, и тд), по окончании статьи.
И первое что нужно знать это то, что язык, который мы изучим называется Object Pascal, люди далекие от ИТ и программирования называют его Delphi (по названию самого популярного компилятора (редактора кода) для этого языка).
(Не знаю почему это никто не читает, но эта статья не будет интересная тем, кто уже знаком с программированием, она будет интересная начинающим программистам!)
я планировал цикл из 5 статей посвященный всем основным языкам (с++, паскаль, джава, си шарп, веб), но налетевшие
эксперты программирования(*ыдлокодеры) , которые даже статьи толком не читали вызвали у меня жесткий бадхерт и дезморал, из за чего желание писать статьи упало на пол шестого, как и пребывание на этом паблике, поэтому сорянчик:(
Итак, ближе к делу.
Создадим программу, которая будет разбирать текст в редакторе, и (на основании команд из редактора) рисовать фигуры определенного размера в определенном месте на специальной области справа:
Немного теории и много практики, также тебе будет гораздо проще если ты перед прочтением этой статьи, прочитаешь статью, посвященную тому «как войти в айти с 0 за час, на языке С++»
Чуть-чуть теории и много практики.
Программирование построено на 4 основных концептах:
1) Переменные
2) Конструкции условий
3) Конструкции циклов
4) И конструкции функций
В двух словах мы хитроумно играем с базовым функционалом, предоставляемым на уровне процессора\биоса\системы, комбинируем те базовые функции с какими‑то данными из переменных, добавляем условия и циклы и оборачиваем это все в новые функции.
К примеру:
У нас есть функция на уровне системы (для изменения цвета пикселя на экране, которая выходит от драйвера видеокарты, а та из комбинированной работы процессора и видеокарты).
Мы несколько раз вызываем эту функцию (в каких‑либо координатах), таким образом рисуем линию, оборачиваем это в функцию для рисования линий, 4 раза вызываем эту функцию чтобы нарисовать (к примеру) кнопку, потом оборачиваем это в функцию для рисования кнопок и уже рисуем в зависимости от положения курсора разными цветами, и тд.
Надеюсь, логика понятна, немного по сути:
Переменная — условный «маркер данных», подстановочное значение, ключевое слово, с которым что‑то ассоциировано, хранилище данных.
Конструкция условий — какое либо правило для выполнения одной части кода, либо альтернативной (состоит из заголовка «условия» и блока кода, между ключевыми словами begin и end «тела»).
Конструкция цикла — тоже условие, но вместо альтернативного, когда повторяет код внутри тела (между ключевыми словами begin и en), до тех пор, пока условие выполняется.
Функция — ключевое слово, которое выполняет какой‑либо блок кода, и возвращает на место своего использования какие‑либо данные (функция может не только возвращать какие‑либо данные, но и принимать в себя данные (аргументы функции), которые вместе с функцией указаны в месте вызова, в коде, функция также состоит из заголовка и тела).
Нечто важное, что нужно знать это «типы данных» для переменных, существуют стандартные числовые и строковые, но большая часть «типов данных» это пользовательские типы данных, которые могут состоять как из 2 числовых переменных в одной, так и из нескольких окон программы в одной переменной (иначе говоря типы данных для переменных позволяют создавать любые переменные, а эти «любые переменные могут иметь внутри себя, как просто число, так и функции, циклы, условия, переменные, и другие, кнопки, окна, и тд).
Ничего страшного если непонятно разберемся на ходу.
Последнее из теории это GetMessage() и SendMessage(), две ключевых функции, на которых построены все графические приложения, первая получает сообщения от системы, вторая отправляет сообщения в систему.
К примеру: получаем от системы сообщение «координаты мыши изменены», в коде создаем условие, проверяющие пересекают ли новые координаты местоположение кнопки, и, если да, меняем цвет кнопки. Отправляем сообщение примерно также (из программы в систему), и такой цикл происходит все время, пока запущенна программа (система: нажата кнопка, программа: изменен размер окна, система: создан новый файл, программа: окно перерисовано, и тд).
А для тех, кто уже читал мою предыдущую статью расскажу в двух словах:
Object Pascal по сути ни чем не отличается от С++ (кроме синтаксиса), вся архитектура и подходы идентичны, за исключением одного большого минуса — Особенности концепции языка предполагают создание новых переменных только в заголовках, при этом наполнение (ассоциация) переменных с какими то данными не может происходить в моменте создания переменой (что в принципе логично, так как они объявляются в заголовке, а не в теле, где происходит реализация).
Это является очень большим минусом языка (вообще синтаксис (в целом) объектного паскаля крайне паскудный и шакальный), в случаях если у тебя в коде есть очень емкие функции, тебе приходится без конца прокручивать мышкой вверх‑вниз чтобы добавить переменные.
Еще у паскаля есть одна уникальная причуда, это процедуры (наравне с функциями), так как это высокоуровневый язык программирования (спроектированный под концепцию ооп) работа предполагается со сложно составными объектами (с классами), и, специально для свойств внутри этих классов (для переменных внутри класса, класс — это пользовательский тип данных) были созданы процедуры (для изменения содержимого, которое ассоциировано со свойством).
Не в обиду разработчикам с++ и с (которые работали всю жизнь только с С, С++, «сибразины»), но у паскаля есть множество неоспоримых плюсов перед с++ (хе‑хе ну вы поняли с++ и плюсы), просто мало кто о них знает в меру собственных знаний.
К примеру, богатая палитра Com объектов (компонентов, или готовых шаблонов для кнопок, полей ввода текста, картинок, и тд, кароч не надо изобретать велосипеды).
Полная изолированность от низкоуровневого программирования, но с прямым доступом (при помощи языка assembler, который можно встраивать прямо в код для работы с объектами), при необходимости.
Отсутствие мусора обратной совместимости (в с++ очень много библиотек, созданных в концепции «индуиского кода», который непонятно как работает, и зачастую многие такие «библиотеки» могут вызывать ошибки на пустом месте из‑за конфликтов друг с другом, к примеру с доступом к памяти для переменных).
Даже несмотря на то, что мой любимый язык это C# (за простоту и за «есть все из коробки»), больше всего программ я написал именно на паскале.
Много практики.
Запускаем редактор (я буду использовать Lazarus, вы можете какой ни будь другой, они все одинаковы), у нас «из коробки» сразу есть готовая форма и редактор для «натыканья» компонентов на форму программы.
Побалуемся, запустим, все работает, отлично закрываем. Разберем немного сгенерированный код.
НАЧИНАЕМ НАЧИНАТЬ
// ключевое слово юнит определяет название,
// куска кода в файле, то есть если мы где то
// в коде используем слово Unit1, на его место
// будет вставлено все содержимое этого файла
// (или не все, так как компилятор вырежет из
// файла только использованные функции, и вставит
// их
unit Unit1;
// над этим пока заморачиваться не нужно, можно пропустить
//
// это команда для компилятора, которая выполняется в момент
// перед генерацией скомпилированного файла нашего приложения
// в данном случае команда {$mode objfpc} заставит редактор кода
// сгенерировать файл с готовым кодом в стиле (скомпилированным)
// лазаруса, команда {$H+} заставит редактор конвертировать
// все содержимое этого файла в кодировку Ansi
{$mode objfpc}{$H+}
// ключевое слово интерфейс, пока не нужно забивать себе голову
// и можно пропустить
//
// ключевое слово interface говорит о том что все описанное ниже
// (наш условный тип данных) является шаблоном для того чтобы на
// базе нашего типа данных создавать другие (дочерние) типы данных
//
// если в с++ концепция ооп была просто условностью, то в object
// pascal это необходимость, продиктованная использованием COM
// объектов (заготовок кнопок, текстовых полей, и тд), в с++
// все переменные имеют строгий размер и статичны, в паскале нет,
// (это основное отличие от с++), то есть тут могут быть переменные
// (или мультипеременные), которые на ходу могут изменять свой
// максимальный размер, удалять данные, изменять данные, и тд
//
// мало кто помнит, но концепция интерфейсов появилась вместе с
// концепцией COM объектов, интерфейсы, по сути, это абстрактные
// классы (типы данных), или один большой шаблон для заголовка
// пользовательского типа данных, нашим типам данных (для того
// чтобы быть совместимыми с COM объектами из библиотек), нужно
// иметь какие-либо универсальные свойства (для всех объектов, как
// доступных "из коробки", так и наших, потому что в нашу программу
// не встроена логика для работы с данными, которые еще неизвестны
// универсальным функциям, но функции должны уметь их обрабатывать)
//
// к примеру у нас есть функция FindComponent (ее модификация,
// которая находит компонент по имени типа данных), она должна
// каким-то образом узнать сколько (и каких) компонентов на форме
// в данный момент, а потом еще догадаться каким именем обладает
// наш тип данных, для этого у нас есть базовый тип данных, от
// интерфейса (шаблона), от которого в роли наследуемых (вместе с
// базовыми свойствами, и решениями из шаблона) выступают все
// компоненты (включая компоненты "из коробки" и наши), а так
// как шаблоном (интерфейсом) определено наличие свойства name,
// даже если оно не описано и не задействовано (и разработчик
// даже не знает о нем), базовый функционал сам даст ему уникальное
// имя, по которому функция FindComponentByName сможет его найти,
// а учитывая, что все компоненты описаны на общем интерфейсе
// разгадать свойства и поведение (для какой-нибудь функции
// аналогичной FindComponent) не составит труда
//
// разумеется, с таким подходом необходимо изолировать низкоуровневые
// процессы (к примеру выделение памяти под переменные) и передать
// в автономный обработчик, который анализирует через интерфейс
// потребности объекта, поэтому такой подход к программированию
// называют высокоуровневым (ничего страшного если не понятно,
// станет понятно по ходу)
interface
// и начать отсюда
// подключаем сторонние библиотеки по аналогии с Unit (вверху),
// берет готовые юниты и добавляет какие-то фрагменты их
// содержимого сюда, через запятую перечисляются все подключаемые
// библиотеки
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Grids, Unit2;
// ключевое слово, после которого идет описание нашего типа данных,
// (или заголовка нашего типа данных) в данном случае так как наш
// тип данных может выступать родителем для других, и соответствуют
// концепции интерфейсов, мы определяем ожидаемое содержимое в нем
// (для других классов, дочерних от этого)
type
{ символом {} фигурных скобочек в паскале обозначены комментарии
(мультистрочные) }
{ TForm1 }
// задаем ключевое слово (название нашего пользовательского
// типа данных), далее символом = указываем редактору что это ключевое
// слово "соответствует" (в паскале "=" является указателем соответствия)
// классу (или болванке для типа данных,
// на базе универсального типа данных COM), после чего в ()
// круглых скобках указываем что наш тип данных должен быть
// унаследован от шаблона формы окна программы (тип данных TForm),
// благодаря чему в нашем типе данных "из коробки" есть пустая форма
TForm1 = class(TForm)
// так как наш тип данных не просто унаследован от другого типа
// данных, но и сам может быть типом данных прародителем, описываем
// в заголовке содержимое, которым мы "расширили" тип данных TForm,
// чтобы потенциальный дочерний тип данных знал, что ему нужно
//
// по сути, мы тут просто создаем переменные с компонентами (полно
// функциональные заготовки, с которыми потом ассоциируем результат
// создания нового экземпляра какого-либо объекта), просто
// я попутно объясняю суть ооп, сейчас вся наша программа состоит
// из наследованных типов данных, в которых уже есть весь функционал
//
// объявим переменную типа заготовка для кнопки, для создания
// переменной нужно написать ключевое слово var, после чего любое
// имя переменной, и в конце тип данных, который можно ассоциировать
// с этой переменной
Button1: TButton;
// объявим переменную типа заготовка для выпадающего списка
ComboBox1: TComboBox;
// объявим переменную типа поле ввода текста
Edit1: TEdit;
// объявим переменную типа "готовый эксель"
StringGrid1: TStringGrid;
// объявим заголовок процедуры (аналог функции, просто
// не возвращает какой-либо результат на место своего
// вызова), в качестве данных, передаваемых в функцию
// передаем переменную, которая (тип данных TObject
// универсальный), которая вызвала эту процедуру,
// указываем псевдоним, под которым внутри процедуры
// эта переменная будет доступна как Sender, саму процедуру
// назовем FormCreate
//
// на самом деле этот код был весь сгенерирован и добавлен
// автоматически (чудеса высокоуровневого программирования)
procedure FormCreate(Sender: TObject);
// private указатель на секции доступности содержимого
// нашего типа данных для других классов, все описанное
// ранее находится по умолчанию в другой секции (она
// называется published, секция специально для работы в ран
// тайме), пока не надо загружаться, можно пропустить
private
// общедоступная секция
public
// конец заголовка нашего типа данных
end;
// объявляем переменную типа наш тип данных (TForm1)
// под названием Form1, обратите внимание на хитроумный прием,
// создание переменной Form1 происходит вне заголовка, или
// тела нашего типа данных, то есть файл сам по себе по
// мимо того, что является источником шаблона для себя, определяет
// свое собственное создание
var
Form1: TForm1;
// ключевое слово implementation говорит редактору
// что все дальше будет "телом" нашего типа данных,
// или логикой работы нашего типа данных (все это
// также будет включено в состав интерфейса TForm1,
// на базе которого также описан класс (пользовательский
// тип данных) TForm1
implementation
// команда для компилятора, $R, если задано какое либо
// значение после команды $R, редактор кода попробует
// найти в одной папке с нашим исходим файлом, файл
// ресурсов с указанным именем, так как в параметре
// команды указано *.lfm, это подразумевает найти и
// добавить на место этой команды все содержимое всех
// файлов в папке с юнитом, в которых тип файла указан .lfm,
// в этом файле содержатся координаты объектов, которые я
// накидал на форму, и другие параметры
{$R *.lfm}
// опять комментарий
{ TForm1 }
// реализация процедуры FormCreate (мы ранее описывали
// ее в заголовке, сейчас мы ее реализуем), так как
// процедура (читай функция) FormCreate является частью
// интерфейса TForm1, мы указываем имя функции (процедуры)
// через "." - универсальное обращение к свойствам (переменным),
// и методам (функциям) класса (нашего пользовательского
// типа данных)
procedure TForm1.FormCreate(Sender: TObject);
// ключевые слова begin и end формируют собой "блочок" кода,
// который должен принадлежать к какой-либо конструкции,
// то есть отдельный фрагмент кода для последовательного
// выполнения
begin
// внутри функции я просто обращаюсь к свойству (мультипеременной
// Cells, она же "массив"), и меняю значение записей, так как
// Cells это свойство объекта StringGrid1 (его мы также объявили
// в заголовке нашего типа данных), соответственно данными
// манипуляциями я просто ассоциирую какой-либо текст с конкретной
// записью мультипеременной, которая отвечает за отрисовку содержимого
// конкретной ячейки, исходя из ее порядкового номера в мультипеременной
// (двойной мультипеременной, или же матрице, это когда каждая переменная,
// внутри мультипеременной Cells сама является мультипеременной)
StringGrid1.Cells[0,0]:='б';
StringGrid1.Cells[1,0]:='ы';
StringGrid1.Cells[2,0]:='д';
StringGrid1.Cells[3,0]:='л';
StringGrid1.Cells[4,0]:='о';
StringGrid1.Cells[5,0]:='к';
StringGrid1.Cells[6,0]:='о';
StringGrid1.Cells[7,0]:='д';
StringGrid1.Cells[8,0]:='е';
StringGrid1.Cells[9,0]:='РРР';
// конец нашей процедруы
end;
// конец реализации нашего типа данных, который начался
// после ключевого слова implementation
end.
Надеюсь, стало хоть немного понятно (несмотря на то что строк очень мало, код очень сложный). Так как мы истинные "делфи программисты" (паскаль), откроем меню проекта, и оттуда исходный файл проекта. Этот файл также называют файлом начальной загрузки, или бутстрап, он же само загрузочный файл, файл инициации, и тд.
Рассмотрим его:
ДОБАВИМ КУСОК В ПРОГРАММУ
// наша программа (ее "заголовочный"
// блок кода)
program project1;
// выбираем режим компиляции этого файла
{$mode objfpc}{$H+}
// подключаем библиотеки
uses
// указываем компилятору условие,
// в зависимости от которого подключаем
// библиотеки для работы с мультипоточностью
{$IFDEF UNIX}
cthreads,
{$ENDIF}
{$IFDEF HASAMIGA}
athreads,
{$ENDIF}
// подключаем библиотеки
Interfaces, // this includes the LCL widgetset
Forms, Unit1
{ you can add units after this }
,Classes, SysUtils, Controls, Graphics, Dialogs, StdCtrls, Grids;
// подгружаем на место команды содержимое всех файлов
// с расширением *.res
{$R *.res}
// блочок кода тела нашей программы
begin
// в глобальную переменную нашей программы, которая
// отвечает за проверку ресурсов, перед загрузкой в
// формы помещаем false, чтобы программа не выдавала
// фатальных ошибок, и просто отобразила пустую форму,
// оператор присвоения (ассоциации данных, с каким то
// ключевым словом) это ":=", или математический аналог
// присвоения
RequireDerivedFormResource:=True;
// в переменную, которая отвечает за масштабирование
// окон нашей программы помещаем true (иначе говоря
// включаем масштабирование, у объекта Application)
Application.Scaled:=True;
// вызываем функцию, которая выполнит нормализацию,
// и регистрацию нашей программы в системе
Application.Initialize;
// вызываем функцию, которая создает новое окно программы,
// в качестве параметров (аргументов функции, или в нашем
// случае процедуры), передаем тип данных, которыми нужно
// попробовать создать окно программы, и имя переменной,
// с которой нужно ассоциировать результат создания
// нового экземпляра окна программы, типом данных передаем
// TForm1, переменной Form1
Application.CreateForm(TForm1, Form1);
// запускаем бесконечный цикл отлова сообщений
// от системы, и передачи сообщений в систему
Application.Run;
end.
Вообще существует хороший способ определить программиста, дело в том, что под понятием бутстрапа изначально понимался подход к разработке программ, где основной файл содержал минимум кода, и просто служил триггером для запуска всех решений (хранил функцию main, winmain, initapplication, и тд), которые были описаны в других файлах. В зависимости от ответа собеседника можно сделать выводы, так как именно с приходом делфи появилось первое понятие «быдлакодер», и такие программисты (на самом деле независимо от языка) легко себя выявляли. Врочем я отхожу от темы, если сейчас все еще ничего непонятно, это не проблема, перейдем к коду, добавим этот код между Application.CreateForm(TForm1, Form1); и Application.Run;
// команду, которая отвечает за создание и показ готового
// диалогового окна с текстом и кнопкой ok, для того чтобы
// текст сообщения воспринимался редактором как "текст",
// а не как команда, или ключевое слово, берем его в
// одиночные кавычки
ShowMessage('ololo');
Теперь давайте попробуем создать кнопку (а за одно удалить компоненты, которые уже есть на форме), для начала переместимся в секцию до начала тела программы, создадим переменную:
// объявим переменную типа заготовка кнопки
var MyBTN: TButton;
После чего в теле программы напишем это:
// с переменной типа заготовка кнопки ассоциируем
// результат выполнения функции Create, которая создаст
// новый экземпляр объекта кнопка, в соответствии с шаблоном
MyBTN:= TButton.Create(Form1);
// поместим созданную кнопку на Form1, через указание в свойстве нашей
// кнопки Parent, имени объекта, который должен владеть этой кнопкой,
MyBTN.Parent:=Form1;
// обратимся к переменной Caption, которая является
// унаследованным (от шаблона типа данных TButton)
// свойством объекта, с которым ассоциирована надпись
// на кнопке, и ассоциируем с переменной Caption текст,
// после чего переменная запустит функцию (унаследованную
// от шаблона типа данных TButton), которая изменит надпись
// на кнопке
MyBTN.Caption:='ААА КНОПКААААА';
Запустим и увидим сообщение, далее жмем ок и запускается форма:
Теперь на форму можно накидать несколько кнопок и полей ввода, дополним наш код, объявим пару переменных после кнопки:
iA, iB:integer;
А тело программы дополним кодом (между Application.CreateForm(TForm1, Form1); и Application.Run;)
ДОБАВИМ КОД УДАЛЕНИЯ КОМПОНЕНТОВ В РУНТАЙМЕ
// создадим условие, если переменная ComponentCount,
// которая была наследована от типа данных TForm
// и при запросе которой срабатывает функция
// для поиска и подсчета всех компонентов на форме,
// вернула на место своего вызова значение большее
// чем 0, выполняем код условия (внутри условия,
// то есть между begin и end), переменная ComponentCount
// это так называемое "свойство класса", или переменная,
// которой назначены две функции, первая чтобы получить
// значение в переменную, вторая чтобы изменить значение
// переменной (Get и Set функции, но в паскале для Set процедура)
if Form1.ComponentCount > 0 then
begin
// показываю сообщение, к которому добавляю количество
// компонентов на форме, из переменной ComponentCount,
// которая есть у моей формы из наследованного типа данных
// TForm, и в которую при помощи специальной функции попадает
// количество компонентов на форме (при помощи метода Get,
// у свойства ComponentCount, если назвать это иначе)
ShowMessage('Слишком много компонентов, надо удалить '+IntToStr(Form1.ComponentCount));
// переместимся в секцию begin и end начала программы,
// и объявим переменную счетчик, а с переменной ассоциируем
// результат выполнения (встроенной в тип данных TForm)
// функции для получения количества компонентов на форме,
// которая отправляет данные в переменную ComponentCount,
// при любой попытке запросить значение у переменной ComponentCount,
// так как в программировании все отсчеты начинаются с 0,
// из количества компонентов вычитаю 1 (то есть 0 это не ничего,
// 0 - это первый элемент списка, а ComponentCount вернет
// количество, считая с 1)
iA:=Form1.ComponentCount - 1;
// запускаем цикл с условием, повторять действие внутри
// цикла, до тех пор пока содержимое переменной iA больше,
// или равно 0, цикл похож по конструкции на условие, только
// повторяет код внутри своего блочка с кодом (между begin и end)
while iA >= 0 do
begin
// задаю условие, если наследованное от типа данных TForm
// свойство класса ClassName вернуло на место своего вызова
// ключевое слово, которое соответствует заданному мной
// ключевому слову (TEdit), выполняю код внутри блочка условия
if Form1.Components[iA].ClassName = 'TEdit' then
begin
// так как удалить компонент я не могу (он находится
// в процессе использования формой, так как вся конструкция
// нашего кода находится между командами CreateForm и Run),
// я отправляю свойству Visible, у компонента, который я
// интерпретирую (через TEdit()) как компонент TEdit,
// которым он и является, значение false, где уже переменная
// Visible (свойство класса, иначе говоря) вызовет функцию Set,
// которая сделает компонент невидимым
TEdit(Form1.Components[iA]).Visible:=false;
// вывожу сообщение с именем объекта
ShowMessage(Form1.Components[iA].Name + ' удалено');
end;
// аналогично для всех компонентов на форме с именем класса
// TButton
if Form1.Components[iA].ClassName = 'TButton' then
begin
TButton(Form1.Components[iA]).Visible:=false;
ShowMessage(Form1.Components[iA].Name + ' удалено');
end;
// TStringGrid
if Form1.Components[iA].ClassName = 'TStringGrid' then
begin
TStringGrid(Form1.Components[iA]).Visible:=false;
ShowMessage(Form1.Components[iA].Name + ' удалено');
end;
// и TComboBox
if Form1.Components[iA].ClassName = 'TComboBox' then
begin
TComboBox(Form1.Components[iA]).Visible:=false;
ShowMessage(Form1.Components[iA].Name + ' удалено');
end;
// также есть универсальный метод для COM объектов
// Form1.Components[iA].Destroying;
iA:= iA - 1;
end;
end;
Мало кто знает, но концепция ооп была создана не только для решения «кризиса наименований», в первую очередь она призвана обеспечивать приложениям «динамическость», или же возможность работы с поведением программы после начала выполнения программного кода, это называется RunTime, или RT (поэтому фраза ООП в php, и веб разработке звучит как минимум странно, где «время жизни» объектной модели заканчивается на отправке страницы клиенту).
Обратите внимание что я не разу не обратился к конкретному объекту на форме, только к теоретическим ожидаемым данным, которые я могу предугадать благодаря концепции ООП и интерфейсам (абстрактным классам, или абстрактным типам данных). Вообще концепция ООП очень полезная и интересная штука, просто по началу кажется немного сложной, на деле в ней нет ничего сложного.
Для полноты понимания работы объектов COM лучше всего узнать за что отвечают их универсальные типы данных, которые являются прародителями (TObject и TComponent), а также посмотреть на то, как работают их универсальные свойства (переменные типа данных).
Сейчас еще может быть непонятно, поэтому тык по «файл\создать модуль», у нас сразу сгенерируется пустая заготовка интерфейса, к которой сразу подключены базовые библиотеки
Наполним файл кодом (так как object pascal использует COM объекты, придется (в отличии от с++ где с этим проще) работать исключительно на «эталонном» ооп, это не просто поэтому рекомендую познакомится с моей предыдущей статьей (перед началом):
ПРОДОЛЖАЕМ ПРОДОЛЖАТЬ
unit Unit2;
// способ компиляции файла
{$mode ObjFPC}{$H+}
// указатель того, что наш тип данных может быть интерфейсом
interface
// подключим библиотеки
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Grids, Menus;
// создадим наш тип данных, после ключевого
// слова type вводим любое имя нашего "класса",
// затем продолжаем логическую конструкцию,
// символом (=) ровно, и ключевым словом class,
// всю строку можно прочитать как
// тип ТекстовыйЗагрузчикФайлов соответствующий классу,
// модификаторы доступа (public, private, и тд) можно
// не писать для простых "составных" типов данных
type TextFileLoader = class
// создадим переменные
//
// объявляю переменную типа TStringList,
// это заготовка типа данных, которая содержит
// в себе все необходимое для загрузки текстового
// файла в программу
FileLoader:TStringList;
// создам переменную типа TMemo, это заготовка
// типа данных, в которой уже есть готовый текстовый
// редактор, при помощи этой переменной я буду
// отображать текст из файла
TextViewer:TMemo;
// создам еще одну переменную типа TStringList,
// на этот раз для сохранения файла
FileSaver:TStringList;
// создам переменную типа PopupMenu, для создания
// всплывающего меню, при правом тыке в текстовом
// редакторе
TextViewerMenu:TPopupMenu;
// добавим переменную с диалоговым окном выбора файла,
// эта заготовка также из библиотек, для загрузки,
// и сохранения файла я буду использовать один и тот
// же диалог
SelectFile:TOpenDialog;
// теперь опишем заголовки функций (для реализации
// процесса загрузки файла, создания в окне программы
// поля для отображения и редактирования текста, и тд)
//
// сначала заголовок конструктора (конструктор - это функция,
// которая выполняет в момент создания нового экземпляра
// объекта нашего типа данных, и ассоциируется с какой либо
// переменной (которая таже имеет тип данных "наш тип данных",
// или TextFileLoader функция конструктора начинается ни со
// слова function, в качестве параметров в функцию мы будем
// передавать в конструктор идентификатор окна программы, в котором
// будем разворачивать наш текстовый редактор, так как все наши окна
// являются производными от COM объекта TForm (или заготовки формы),
// мы можем передавать в наш конструктор любую форму, которая
// была наследником типа TForm, компилятор сделает все остальное сам
constructor New(Owner:TForm);
// помимо конструктора нужно объявить деструктор, это тоже функция,
// но для описания логики разбора и уничтожения теоретической
// переменной, с которой ассоциирован экземпляр нашего TextFileLoader,
// но так как это высокоуровневое программирование, деструктор не
// обязательно описывать, потому что по завершении любого блочка кода,
// в рамках которого была объявлена эта переменная, компилятор сам
// уничтожит переменную (и все остальные, в рамках этого блочка),
// если параметры в функцию не нужны, скобки () тоже можно не указывать,
// и если что компилятор также игнорирует регистр, то есть TEXTFILELOADER
// и TextFileLoader - это одно и тоже, несмотря на то что коды символов
// у больших и маленьких букв разные (это высокоуровневое программирование)
destructor DestructeObject;
// добавим заголовок функции для загрузки файла, я использую процедуру,
// но это та же самая функция, просто не возвращает на место своего
// вызова какой-либо результат
function LoadFile:string;
// заголовок функции, которая будет сохранять данные в файл, в себя
// функция будет получать новый текст файла, в которой нужно сохранить
// текст, так как я не планирую что наша функция что-то будет возвращать,
// поэтому опишу функцию как процедуру (тоже самое что и функция, но
// не возвращает данные на место своего вызова)
procedure SaveFile(NewText:string);
// теперь создадим "обертку" для нашей функции, чтобы использовать в меню,
// из-за ограничений концепции ооп мы должны привести нашу процедуру
// к "универсальному виду", все процедуры и функции, которые назначаются
// к событиям COM объектов должны принимать в себя ссылку на объект,
// который вызвал эту функцию
procedure SaveFileClick(Sender:TObject);
// добавлю специальную функцию для сброса файла к исходному (к моменту
// загрузки в редактор), так как TStringList сохраняет в себе текст
// после использования, так как возврат значения не обязателен, опишу
// функцию как процедуру
procedure ResetFile;
// создадим свойство класса ("условную переменную", по к которой
// привязана функция, которая получает данные, которые просто так
// не извлечь из переменной), это свойство будет маркером данных,
// везде, где оно будет встречаться в коде, оно будет заменено на
// результат выполнения функции, это свойство будет возвращать на
// свое место оригинальный текст, свойство начинается с ключевого
// ключевого слова property, далее идет любое название этого свойства,
// затем тип данных, который может принять в себя и вернуть свойство,
// затем (через пробел) ключевое слово READ, которое обозначает
// получение данных, и имя функции, я назову свойство
// GetContentFromTextFileLoader, а в качестве функции использую
// SaveFile, ключевое слово public позволяет установить модификатор
// доступа, если установлен public, значит к этому свойству можно
// будет обратится не только из тела нашего типа данных, но и везде из кода
PUBLIC PROPERTY GetContentFromTextFileLoader:string READ LoadFile;
// опишем заголовок этой функции, тип данных, которые функция
// будет возвращать на свое место будет текст (string)
// создадим еще одно свойство, оно будет собирать и сразу
// сохранять данные в файл, при попытке изменить содержимое
// свойства (изменить данные, которые ассоциированы с этим
// свойством), для этого опишем свойство аналогично предыдущему,
// вместо функции для чтения, определим функцию для записи,
// свойства класса позволяют использовать для одно свойства
// одновременной и READ, и WRITE, так как функции нужны данные,
// которая она в себя принимает при вызове, свойство автоматически
// в качестве данных передаст значение, которое пытаются
// ассоциировать со свойством, где-то в месте (в коде), где
// вызвано это свойство
PUBLIC PROPERTY SetNewContentToTextFileLoader:string WRITE SaveFile;
//public PROPERTY OnMyEvent : TNotifyEvent READ LoadFile WRITE SaveFile;
end;
// тело нашего типа данных (место, где описываются
// функции и логика работы нашего типа данных
implementation
// для реализации функции надо указать заголовок функции, и ключевые слова
// begin и end, но для того чтобы редактор смог понять от какого типа данных
// эти функции, названия функций надо начинать с типа данных, к которому
// принадлежат эти функции (в одном файле может быть более одного типа данных,
// так как это интерфейс
//
// реализуем логику создания нового экземпляра нашего типа данных
constructor TextFileLoader.New(Owner:TForm);
begin
// ассоциируем с переменной FileLoader результат
// создания нового объекта из заготовки TStringList,
// символ := (математическое присвоение) обозначает
// присвоить (ассоциировать) ключевому слову слева
// ключевое слово справа
FileLoader:= TStringList.Create;
// создадим из заготовки всплывающее по правому тыку меню
TextViewerMenu:= TPopupMenu.Create(TextViewer);
// добавим элемент меню, для этого в функцию Add нашего
// объекта меню передать элемент меню с типом данных TMenuItem,
// а для этого мы обращаемся к COM заготовке с соответствующим
// названием (типа данных TMenuItem), и у заготовки вызываем
// функцию создания нового элемента меню (функцию конструктор,
// Create), у которой в качестве родителя указываем наш элемент
// меню (можно указать что угодно), в результате функция Create
// возвращает на свое место новый экземпляр объекта меню,
// который сразу же оказывается качестве параметра для функции
// Add (у нашего меню), и добавляется в меню
TextViewerMenu.Items.Add(TMenuItem.Create(TextViewerMenu));
// так как мы знаем, что это первый элемент (отсчеты начинаются с 0),
// обращаемся к нему и изменяем текст элемента меню
TextViewerMenu.Items[0].Caption:='Сохранить';
// добавим (привяжем) функцию к событию нажатия по элементу меню,
// символ собачки (@) для обратного управления с низкоуровневым
// программированием, он позволяет "ассоциировать" какой либо
// объект с чем-либо (в данном случае я беру ключевое слово, которое
// является "указателем" на фрагмент кода, и ассоциирую его со значением,
// которое должно находится в стандартном событии OnClick), то есть
// я работаю не с прямыми объектами, а с указателем на конкретные данные,
// в низкоуровневом программировании, также есть символ (^) - стрелка вверх,
// которая позволяет создавать указатели на данные (ключевое слово, которое
// напрямую не может содержать в себе какие-либо данные, только ссылку на
// другое ключевое слово, или ссылку на конкретные (в памяти) данные),
// особенности стандартного свойства OnClick (тип данных TNotifyEvent
// позволяет это сделать), все свойства COM объектов, связанные с событиями
// имеют стандартный тип данных TNotifyEvent
TextViewerMenu.Items[0].OnClick:=@SaveFileClick;
// ассоциирую с переменной TextViewer результат
// создания (вызова конструктора у типа данных TMemo)
// нового объекта текстового поля (из заготовок COM),
// функция Create принимает в себя идентификатор объекта,
// который является прародителем, в данном случае я передаю
// туда объект, который будет является прародителем нашего типа
// данных, идентификатор которого также будем получать при
// вызове этой функции (New)
TextViewer:= TMemo.Create(Owner);
// разместит редактор на форме
TextViewer.Parent:=Owner;
// перепозициионируем, изменим отступ слева (от края окна),
// отступ сверху на 100 пикселей
TextViewer.Left:=100;
TextViewer.Top:=100;
// изменим размер редактора, исходя из размеров
// передаваемого во время вызова функции окна программы,
// а именно его ширины, и высоты, из которых вычитаем
// 200 пикселей (указатель на окно программы передается в
// свойстве Owner)
TextViewer.Width:=Owner.Width - 200;
TextViewer.Height:=Owner.Height - 200;
// добавим меню по правому тыку (мы создавали его чуть выше)
TextViewer.PopupMenu:=TextViewerMenu;
// создадим TStringList для сохранения файла
FileSaver:= TStringList.Create;
// создадим из заготовки стандартное диалоговое
// окно с выбором файла, все что оно делает - это
// открывается, при выборе файла закрывается, наполняя
// свое свойство FileName путем к выбранному файлу
SelectFile:=TOpenDialog.Create(Owner);
end;
// опишем деструктор (это не обязательно, но так как я его объявил
// в заголовке класса, реализуем)
destructor TextFileLoader.DestructeObject;
begin
// inherited это специальная команда, ключевое слово, которое
// позволяет выполнить предустановленный (в заготовке класса)
// код и логику, командой inherited можно изменять порядок,
// либо сначала выполняется наш код, либо сначала код из класса
// родителя (если, конечно, таковой вообще есть)
inherited;
end;
// опишем логику работы функции для загрузки файла
function TextFileLoader.LoadFile:string;
begin
// для начала проверим объект SelectFile, если он уже
// открывался, значит там должен быть к выбранному файлу
//
// создадим условие, если содержимое свойства FileName,
// у объекта SelectFile больше, меньше, чем ничего
// выполняем код условия, иначе откроем диалог
if SelectFile.FileName <> '' then
begin
// внутри просто при помощи стандартных функций,
// доступных у объекта FileLoader (наследованных от
// объекта TStringList) загружаем файл во встроенное
// свойство TStringList, свойство Text, в функцию для
// загрузки файла передаем путь к выбранному в диалоговом
// окне файлу
FileLoader.LoadFromFile(SelectFile.FileName);
// со свойством Text (у редактора текста) ассоциируем
// содержимое аналогичного свойства FileLoader, важный
// момент, так как свойства вызывают функции, а не
// передают напрямую объекты мы ассоциируем именно текст,
// а не свойство объекта Text (у FileLoader), с аналогичным
// свойством редактора (TextViewer)
TextViewer.Text:=FileLoader.Text;
end
// блочок кода для конструкции "иначе"
// (то есть что будем делать, если условие не выполнено)
ELSE
begin
// создаем условие, если готовая функция Execute
// вернула в место своего вызова значение, которое
// соответствует заданному нами значению true, выполняем
// код условия, так как может быть теоретическая ситуация,
// в которой пользователь открыл окно выбора файла, и
// просто закрыл (без выбора файла), значение FileName
// останется пустым, поэтому мы вызываем функцию не
// просто как команду, а в формате условия, Execute
// вернет на свое место true если файл был выбран
if SelectFile.Execute = true then
begin
// делаем все тоже самое что и в основном условии
FileLoader.LoadFromFile(SelectFile.FileName);
TextViewer.Text:=FileLoader.Text;
end;
end;
end;
// опишем логику для сохранения файла, по сути, все тоже
// самое и в обратном порядке, и вместо функции LoadFromFile
// используем функцию SaveToFile
procedure TextFileLoader.SaveFile(NewText:string);
begin
// для начала проверим объект SelectFile, если он уже
// открывался, значит там должен быть к выбранному файлу
//
// создадим условие, если содержимое свойства FileName,
// у объекта SelectFile больше, меньше, чем ничего
// выполняем код условия, иначе откроем диалог
if SelectFile.FileName <> '' then
begin
// свойству Text, у объекта FileSaver присваиваем
// содержимое редактора текста (TextViewer)
FileSaver.Text:=TextViewer.Text;
// сохраняем при помощи вызов готовой функции
// SaveToFile, путь к файлу берем из SelectFile
FileSaver.SaveToFile(SelectFile.FileName);
end
// блочок кода для конструкции "иначе"
// (то есть что будем делать, если условие не выполнено)
ELSE
begin
// создаем условие, если готовая функция Execute
// вернула в место своего вызова значение, которое
// соответствует заданному нами значению true, выполняем
// код условия, так как может быть теоретическая ситуация,
// в которой пользователь открыл окно выбора файла, и
// просто закрыл (без выбора файла), значение FileName
// останется пустым, поэтому мы вызываем функцию не
// просто как команду, а в формате условия, Execute
// вернет на свое место true если файл был выбран
if SelectFile.Execute = true then
begin
// делаем все тоже самое что и в основном условии
FileSaver.Text:=TextViewer.Text;
FileSaver.SaveToFile(SelectFile.FileName);
end;
end;
end;
// опишем логику "обертки" для сохранения файла по клику в меню
procedure TextFileLoader.SaveFileClick(Sender:TObject);
begin
// так как наша оригинальная функция принимает в себя новое содержимое,
// которое мы пытаемся сохранить, мы возьмем текущее содержимое из редактора
// текста, из стандартного свойства редактора Text
SaveFile(TextViewer.Text);
end;
// так как оригинальное содержимое все еще хранится в FileLoader,
// мы можем сбросить текст
procedure TextFileLoader.ResetFile;
begin
// со свойством Text (у редактора текста) ассоциируем
// содержимое аналогичного свойства FileLoader, важный
// момент, так как свойства вызывают функции, а не
// передают напрямую объекты мы ассоциируем именно текст,
// а не свойство объекта Text (у FileLoader), с аналогичным
// свойством редактора (TextViewer)
TextViewer.Text:=FileLoader.Text;
end;
end.
Теперь откроем графический редактор, ткнем в форму (она выберется в свойствах объекта), во вкладке события найдем событие OnShow (FormShow), два раза ткнем в пустое поле рядом и у нас сгенерируется готовый блочок процедуры для этого события, напишем туда это:
TextLDR:=TextFileLoader.New(Form1);
А вверху нашей формы (между заголовком формы, и телом формы программы, где объявленная переменная Form1) допишем это:
// объявляем переменную типа наш типа данных (TextFileLoader), название может
// быть любым, у меня TextLDR
TextLDR:TextFileLoader;
Запускаем и видим наш готовый редактор
Отлично! Теперь, когда у нас есть базовое представление об ООП можно сделать нечто более сложное. Единственное на что нужно обратить внимание — это то, что я использовал пример загрузки текстового файла, для считывания байтов файла, и их интерпретации существуют специальные готовые «файловые потоки», и стандартные функции Windows (ShellAPI), но этого нам хватит за глаза.
Тык по меню «файл\создать модуль», теперь дополним нашу программу:
ПЕРЕЙДЕМ К ТЕХНОКОЛДУНСТВУ
// указываем имя нашей библиотеки
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.
Теперь создадим новую переменную:
// добавим переменную
ololo:TComDrawer;
И в событии OnShow добавим код:
// команда FreeAndNil (функция из библиотек)
// позволяет вне очереди удалить объекты,
// по умолчанию все объекты удаляются в рамках
// завершения своей сеции, но так как переменная
// TextLDR объявлена в секции программы (в главной)
// переменная удалится только после завершения программы,
// так как я не описывал Destructor удалять придется вручную,
// я просто удалю то, что визуально мешает, потому что устал
FreeAndNil(TextLDR.TextViewer);
FreeAndNil(TextLDR);
// создадим наш новый объект
ololo:=TComDrawer.New(Form1);
Запускаем и видим это:
Поздравляю! Теперь ты знаешь Object Pascal, умеешь работать в RunTime, имеешь базовое представление об ООП (и умеешь его использовать), имеешь начальные навыки Assembler, работы с файлами, парсингом, и Com‑объектами.
Осталось только получше познакомится с заготовками (с палитры компонентов), их свойствами, добавить SQL и ты продвинутый Delphi программист.
Полагаю после этой статьи меня заминусят так что я уже не смогу рассказать про другие языки (насколько я понял после -20 рейтинга я получаю бан, я хотел сделать для всех основных — C++, Pascal, Java, C#, и веб девелопЁринга), обида ивановна:( буду дальше писать фанфики по вселенной SCP lol.
Я, конечно, ожидал хейта и токсика (но на этапе веб девелопёринга), но никак ни с десктопа, никогда бы не подумал, что в этих сферах тоже огромное количество токсичных, неадекватных и ЧСВшных *ыдлокодеров.
Но что имеем, то имеем, по крайней мере можно сделать выводы — я тут хоть и недавно, но успел понять, что этот сайт не столько косплей пикабу, сколько адский концентрат раскрутки крупных компаний на рекламу, а также продвижения авторских проектов (ну и еще средство придания ЧСВшному образу некоторых индивидов флера несовершеннолетия)
По поведению пользователей можно сделать выводы, где находится наибольший концентрат неадекватности, и в каких конкретных компаниях (или сферах, не считая веб девелопёринга) лучше не работать (а лучше и вовсе уехать лол) начинающему программисту.
И помни — разум и упорство это ключ к победе!
Спасибо за внимание:)