Вверх ↑
Администрация
Ответов: 15294
Рейтинг: 1518
#1: 2011-06-04 14:11:20 ЛС | профиль | цитата
Что такое блоки?
   В рамках *TCG блок это массив кусков кода целевого языка. При первом знакомстве может показаться, что блок это всего лишь другое название привычного инструмента StringList (списка строк), однако это не так. Разница как раз и заключается в способе хранения данных внутри блока и методах их извлечения наружу, о чем подробно будет рассказано в одном из следующих разделов. В данной же части будут рассмотрены основные блоки, применяемые в пакете Lazarus (и в пакетах вообще), а так же методы работы с ними и их использование для генерации синтаксически верного и достаточно оптимального кода целевого языка проекта.

Создание и подготовка блоков к работе
   В предыдущем разделе уже приводилось то место, где лучше всего создавать блоки (да и вообще любые объекты, используемые в пакете). Это метод create модуля sys:

#hws
func create(entry)
gvar(blk_vars, blk_lvars, blk_init, blk_proc_decl, blk_proc_imp, blk_body, blk)

// create blocks
blk_body = block.reg("class_body").inc()
blk_vars = block.reg("class_vars").inc()
blk_lvars = block.reg("class_lvars").inc()
blk_init = block.reg("class_init").inc()
blk_proc_decl = block.reg("class_proc_decl").inc()
blk_proc_imp = block.reg("class_proc_imp")

// set current block
blk = blk_body
end
   Этот метод делает три важные для разработчика вещи:
1) объявляет набор глобальных переменных blk_XXX, которые доступны из любой части проекта и всегда содержат ссылки на текущие блоки, отвечающие за тот или иной сегмент кода конечного приложения (либо как в пакете Lazarus эти блоки содержат код не всей программы целиком, а только элементов схемы для текущего уровня вложенности)
2) создает собственно объекты блоков (названия блоков по большому счету могут быть любыми, т.к. в дальнейшем элементы должны использовать глобальные переменные для доступа к ним, а не их непосредственные имена)
3) указывает, что текущим блоком(переменная blk) является блок blk_body

Что такое текущий блок и зачем он нужен?
   Текущий блок это тот сегмент кода в конечном приложении, который выполняет основную работу программы и в зависимости от того, в какой "событийной" ветки схемы находится элемент, обрабатываемый кодогенератором в данный момент, этот блок может ссылаться на разные участки кода. Посмотрим на примере как это происходит.

Пример 1: в схеме два элемента - MainForm и Label, при этом событие onStart формы соединено с doCaption надписи (т.е. при старте приложения текст надписи должен стать пустым)
Имеем следующий шаблон кода(все лишнее скрыто), из которого лепиться конечное приложение и который был рассмотрен в предыдущей части:

#pas
Unit hiMainForm1;

interface

uses Classes, SysUtils, FileUtil, LResources, Forms, Controls, StdCtrls;

type
TMainForm1 = class(TForm)
protected
procedure DoCreate; override;
...
public
// --- начало блока PRIVATE VARS
{ ... }
// --- конец блока PRIVATE VARS

// --- начало блока METHODS INT
{ ... }
// --- конец блока METHODS INT
end;

implementation

...

procedure TMainForm1.DoCreate;
// --- начало блока LOCAL VARS
{ ... }
// --- конец блока LOCAL VARS
begin
Caption := 'Form1';
// --- начало блока INIT
{ ... }
// --- конец блока INIT

// --- начало блока BODY
{ ... }
// --- конец блока BODY
end;

// --- начало блока METHODS IMP
{ ... }
// --- конец блока METHODS IMP

end.
После сборки программы должно получиться следующее(все пустые блоки убраны):

#pas
Unit hiMainForm1;

interface

uses Classes, SysUtils, FileUtil, LResources, Forms, Controls, StdCtrls;

type
TMainForm1 = class(TForm)
protected
procedure DoCreate; override;
...
public
// --- начало блока PRIVATE VARS
Label2:TLabel; // <-------- добавилась собственно надпись
// --- конец блока PRIVATE VARS
end;

implementation

...

procedure TMainForm1.DoCreate;
begin
Caption := 'Form1';
// --- начало блока INIT
Label3 := TLabel.Create(self); // <--------- создание надписи и добавление на форму
addWidget(Label2, 60, 30, 40, 30);
Label2.Caption := 'Label'; // <---------- инициализация ее свойств
// --- конец блока INIT

// --- начало блока BODY
Label2.Caption := ''; // <--------- это та самая полезная работа, которую и выполняет наше приложение
// --- конец блока BODY
end;

end.

Пример 2: теперь же усложним схему еще одним элементом - Кнопкой, и надпись будем устанавливать не в событии onStart формы, а в событии onClick кнопки. Конечный код должен быть примерно таким:


#pas
Unit hiMainForm1;

interface

uses Classes, SysUtils, FileUtil, LResources, Forms, Controls, StdCtrls;

type
TMainForm1 = class(TForm)
protected
procedure DoCreate; override;
...
public
// --- начало блока PRIVATE VARS
Button2:TButton; // <-------- добавилась новая кнопка
Label3:TLabel;
// --- конец блока PRIVATE VARS

// --- начало блока METHODS INT
procedure onClick2(Sender:TObject); // <-------- появился обработчик события onClick кнопки
// --- конец блока METHODS INT
end;

implementation

...

procedure TMainForm1.DoCreate;
begin
Caption := 'Form1';
// --- начало блока INIT
Button2 := TButton.Create(self); // <--------- создание кнопки и добавление на форму
addWidget(Button2, 35, 20, 55, 30);
Button2.Caption := 'Push'; // <---------- инициализация ее свойств
Button2.onClick := @onClick2;

Label3 := TLabel.Create(self);
addWidget(Label2, 60, 30, 40, 30);
Label2.Caption := 'Label';
// --- конец блока INIT

// --- начало блока BODY
{ ... } // <---------- этот BODY остался пуст
// --- конец блока BODY
end;

// --- начало блока METHODS IMP
procedure TMainForm1.onClick2(Sender:TObject);
begin
// --- начало блока BODY
Label3.Caption := ''; // <---------- а этот BODY теперь содержит основную полезную часть приложения
// --- конец блока BODY
end;
// --- конец блока METHODS IMP

end.
   Следует внимательно посмотреть на последний код: в нем оказалось два блока BODY. Однако вспоминаем, что кодогенераторы *TCG в каждый момент времени могут находится к контексте только одного элемента и выполнять только один его метод, в котором ничто не мешает временно запомнить ссылку на текущий блок BODY (из переменной blk), назначить новый, и продолжить выполнение процесса кодогенерации. После чего восстановить ссылку обратно. Благодаря этому для всех элементов всегда существует только один набор блоков, куда они и печатают свой код не заботясь о том, что и куда попадет на самом деле. На схеме ниже представлены все этапы этого процесса в более наглядном виде:

Графическое представление процесса формирования кода схемы в *TCG пакетах

   Благодаря такому подходу вызовы процедур и функций целевого языка могут быть не ограничено вложенными друг в друга и их код никогда не будет перепутан в процессе кодогенерации. В тоже самое время представление всей этой кухни ввиде простых методов объекта sys делает разработку прозрачной и не требует от программиста каждый раз заниматься ее реализацией. Например, типичный код GUI элементы выглядит так:


#hws
include("WinControl-proc") // общие ф-ции для всех визуальных элементов

func init
addWidget('TButton') // добавление нового виджета на форму (или любой другой GUI контейнер) с классом TButton
include("WinControl-init") // общий код инициализации всех визуальных элементов
sys.set_undef_field('Caption') // установка св-ва Caption текущего GUI элемента
sys.add_event('onClick', this.props("Data").value) // а это вызов того самого механизма создания процедуры в целевом языке, который на схеме выше представлен блоком onClick
end

func doCaption(text) // метод установки текста надписи
blk.println(this.codename + '.Caption := ', text, ';')
end
   Из приведенного кода видно, что все те действия, которые идут после блока onClick на схеме делаются вызовом всего лишь одного метода add_event и разработчику достаточно иметь только общее представление о его назначении без необходимости разбираться в деталях. Именно в этом и заключается первоначальная сложность при проектировании нового (а в последствии при изучении уже существующего) пакета - нужно хорошо продумать технологию генерации и реализовать минимальный набор методов, который позволит в соответствии с ней быстро и просто создавать элементы без необходимости каждый раз вспоминать как и на что подменять блоки и как потом из них собрать нужный кусок кода конечного приложения.
карма: 26
3
файлы: 1rtcg-code-graph.png [47.6KB] [887]
Голосовали:login, Netspirit, CriDos
Редактировалось 1 раз(а), последний 2015-12-12 16:27:43