Объекты. Занятие 2_7. Часть 8.
В предыдущих частях 6 и 7 мы рассмотрелипостановку задачи об отображении графических примитивов «прямая», «треугольник» и «окружность».
Подготовили классы для их отображения с использованием механизма виртуализации.
Рассмотрели создание объектов (в нашем случае — компонентов).
Отметили, что за удаление компонета из памяти отвечает его «владелец» и что для визуальных компонент владельцем, как правило, является форма. Это происходит автоматически при закрытии программы (или отдельной формы).
Показали, что надо сделать, чтобы компонент стал виден на форме и получал сообщения windows. Также показали, что родительским могут быть и другие компоненты.
Отметили, что созданный объект должен быть уничтожен, как только в нём отпадает надобность и об этом должен позаботиться программист! Иначе будет происходить «утечка памяти».
Напомним ещё раз, что интерфейс программы будет выглядеть следующим образом:

На форме находится кнопка, при нажатии на которую происходит отрисовка примитива.
Тип примитива задаётся с помощью выбора из радиогруппы.
Набор координат будет каждый раз свой в зависимости от типа примитива.
Кроме того, будет осуществляться контроль, все ли поля ввода заполнены. Если поле пустое, то выводится сообщение.
Отображение панели и расположенного на ней холста осуществляется в обработчике формы «onCreate», то есть при создании формы.
Приложение состоит из двух форм. На главной форме расположена кнопка, при нажатии на которую происходит открытие второй формы:
procedure TForm1.Button1Click(Sender: Tobject);
begin
form2.ShowModal;
end;
(не забывайте сделать ссылку на Unit2:
implementation
uses Unit2;
Логика программы и описание классов разнесены по разным модулям. Это сделано для того, чтобы элементы классов, расположенные в секциях private, не были доступны в Unit2 (один из принципов инкапсуляции — скрытие части внутреннего устройства класса).
Приведём ещё раз код модуля Unit1 без комментариев:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
Button1: Tbutton;
procedure Button1Click(Sender: Tobject);
private
{ Private declarations }
public
{ Public declarations }
end;
TpanelImage=class(TPanel)
private
vIm:TImage;
public
vPan:TPanel;
constructor create(vComp:TWinControl);
procedure drow;virtual; abstract;
end;
TLine= class(TPanelImage)
public
x_from,y_from,x_to,y_to:integer;
constructor create(vComp:TWinControl;Color:TColor);
procedure drow;override;
end;
Ttriangle=class(TPanelImage)
public
x_1,y_1:integer; x_2,y_2:integer; x_3,y_3:integer;
constructor create(vComp:TWinControl;Color:TColor);
procedure drow;override;
end;
Tcircle=class(TPanelImage)
public
x_from,y_from,R:integer;
constructor create(vComp:TWinControl;Color:TColor);
procedure drow;override;
end;
var
Form1: TForm1;
implementation
uses Unit2;
{$R *.dfm}
constructor TpanelImage.create(vComp:TWinControl);
begin
Vpan:=TPanel.Create(vComp);
Vpan.Parent:=vComp;
Vpan.Left:=400; Vpan.Top:=100;
Vpan.Width:=420; Vpan.Height:=320;
Vpan.Color:=clRed;
vIm:=TImage.Create(vComp);
vIm.Parent:=Vpan;
vIm.Left:=0;vIm.Top:=0;
vIm.Width:=Vpan.Width-20;
vIm.Height:=Vpan.Height-20;
vIm.Canvas.Brush.Style:=bsSolid;
vIm.Canvas.Brush.Color:=rgb(220,200,225);
vIm.Canvas.Rectangle(0,0,Vpan.Width-20,Vpan.Height-20);
vIm.Canvas.Pixels[0,0]:=clRed;
end;
constructor Tline.create(vComp:TWinControl;Color:TColor);
begin
inherited create(vComp);
vPan.Color:=Color;
end;
constructor Ttriangle.create(vComp:TWinControl;Color:TColor);
begin
inherited create(vComp);
vPan.Color:=Color;
end;
constructor Tcircle.create(vComp:TWinControl;Color:TColor);
begin
inherited create(vComp);
vPan.Color:=Color;
end;
//================================================//
procedure Tline.drow;
begin
vIm.Canvas.MoveTo(x_from,y_from);
vIm.Canvas.LineTo(x_to,y_to);
end;
procedure Ttriangle.drow;
begin
vIm.Canvas.MoveTo(x_1,y_1);
vIm.Canvas.LineTo(x_2,y_2);
vIm.Canvas.LineTo(x_3,y_3);
vIm.Canvas.LineTo(x_1,y_1);
end;
procedure Tcircle.drow;
begin
vIm.Canvas.Ellipse(x_from+R,y_from-R,x_from-R,y_from+R);
vIm.Canvas.MoveTo(x_from-R,y_from); vIm.Canvas.LineTo(x_from+R,y_from);
vIm.Canvas.MoveTo(x_from,y_from-R); vIm.Canvas.LineTo(x_from,y_from+R);
end;
//==========================================================//
procedure TForm1.Button1Click(Sender: Tobject);
begin
form2.ShowModal;
end;
end.
Ввод начальных значений и приведение типов
Теперь рассмотрим модуль Unit2.
implementation
uses unit1;
{$R *.dfm}
var vPanIm:TPanelImage;
procedure prLine;forward;
procedure prTriangl;forward;
procedure prCircle;forward;
procedure TForm2.Button1Click(Sender: TObject);
var i:integer;
begin
i:=form2.RadioGroup1.ItemIndex;
case i of
0: prLine;
1: prTriangl;
2: prCircle ;
end;
end;
procedure prLine;
var x_b,y_b,x_e,y_e:integer;
begin
if form2.LabeledEdit1.Text<>» then x_b:=strToInt(form2.LabeledEdit1.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
if form2.LabeledEdit2.Text<>» then y_b:=strToInt(form2.LabeledEdit2.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
if form2.LabeledEdit3.Text<>» then x_e:=strToInt(form2.LabeledEdit3.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
if form2.LabeledEdit4.Text<>» then y_e:=strToInt(form2.LabeledEdit4.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
(vPanIm as TLine).x_from:=x_b; (vPanIm as TLine).y_from:=y_b;
(vPanIm as TLine).x_to:=x_e; (vPanIm as TLine).y_to:=y_e;
vPanIm.drow;
end;
procedure prTriangl;
var x_1,y_1,x_2,y_2,x_3,y_3:integer;
begin
if form2.LabeledEdit1.Text<>» then x_1:=strToInt(form2.LabeledEdit1.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
if form2.LabeledEdit2.Text<>» then y_1:=strToInt(form2.LabeledEdit2.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
if form2.LabeledEdit3.Text<>» then x_2:=strToInt(form2.LabeledEdit3.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
if form2.LabeledEdit4.Text<>» then y_2:=strToInt(form2.LabeledEdit4.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
if form2.LabeledEdit5.Text<>» then x_3:=strToInt(form2.LabeledEdit5.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
if form2.LabeledEdit6.Text<>» then y_3:=strToInt(form2.LabeledEdit6.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
(vPanIm as TTriangle).x_1:=x_1; (vPanIm as TTriangle).y_1:=y_1;
(vPanIm as TTriangle).x_2:=x_2; (vPanIm as TTriangle).y_2:=y_2;
(vPanIm as TTriangle).x_3:=x_3; (vPanIm as TTriangle).y_3:=y_3;
vPanIm.drow;
end;
procedure prCircle;
var R,x_from,y_from:integer;
begin
if form2.LabeledEdit1.Text<>» then x_from:=strToInt(form2.LabeledEdit1.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
if form2.LabeledEdit2.Text<>» then y_from:=strToInt(form2.LabeledEdit2.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
if form2.LabeledEdit3.Text<>» then R:=strToInt(form2.LabeledEdit3.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
(vPanIm as TCircle).x_from:=x_from; (vPanIm as TCircle).y_from:=y_from;
(vPanIm as TCircle).R:=R;
vPanIm.drow;
end;
//================================================//
procedure TForm2.RadioGroup1Click(Sender: TObject);
var i:integer;
begin
if not(vPanIm =nil) then
begin
vPanIm.Free;//или FreeAndNil
vPanIm:=nil;
end;
i:=form2.RadioGroup1.ItemIndex;
case i of
0: begin
vPanIm:=TLine.Create(form2,clGreen);
form2.LabeledEdit1.EditLabel.Caption:=’X начало’;
form2.LabeledEdit2.EditLabel.Caption:=’Y начало’;
form2.LabeledEdit3.EditLabel.Caption:=’X конец’;
form2.LabeledEdit4.EditLabel.Caption:=’Y конец’;
form2.LabeledEdit1.Visible:=true;
form2.LabeledEdit2.Visible:=true;
form2.LabeledEdit3.Visible:=true;
form2.LabeledEdit4.Visible:=true;
form2.LabeledEdit5.Visible:=false;
form2.LabeledEdit6.Visible:=false;
end;
1:begin
vPanIm:=TTriangle.Create(form2,clBlue);
form2.LabeledEdit1.EditLabel.Caption:=’X вершина 1′;
form2.LabeledEdit2.EditLabel.Caption:=’Y вершина 1′;
form2.LabeledEdit3.EditLabel.Caption:=’X вершина 2′;
form2.LabeledEdit4.EditLabel.Caption:=’Y вершина 2′;
form2.LabeledEdit5.EditLabel.Caption:=’X вершина 3′;
form2.LabeledEdit6.EditLabel.Caption:=’Y вершина 3′;
form2.LabeledEdit1.Visible:=true;
form2.LabeledEdit2.Visible:=true;
form2.LabeledEdit3.Visible:=true;
form2.LabeledEdit4.Visible:=true;
form2.LabeledEdit5.Visible:=true;
form2.LabeledEdit6.Visible:=true;
end;
2:begin
vPanIm:=TCircle.Create(form2,clYellow);
form2.LabeledEdit1.EditLabel.Caption:=’X центр’;
form2.LabeledEdit2.EditLabel.Caption:=’Y центр’;
form2.LabeledEdit3.EditLabel.Caption:=’R радиус’;
form2.LabeledEdit1.Visible:=true;
form2.LabeledEdit2.Visible:=true;
form2.LabeledEdit3.Visible:=true;
form2.LabeledEdit4.Visible:=false;
form2.LabeledEdit5.Visible:=false;
form2.LabeledEdit6.Visible:=false;
end;
end; //завершение case
end;//завершение RadioGroup1Click
//=====================================================//
procedure TForm2.FormCreate(Sender: TObject);
begin
form2.LabeledEdit1.EditLabel.Caption:=’X начало’;
form2.LabeledEdit2.EditLabel.Caption:=’Y начало’;
form2.LabeledEdit3.EditLabel.Caption:=’X конец’;
form2.LabeledEdit4.EditLabel.Caption:=’Y конец’;
form2.LabeledEdit1.Visible:=true;
form2.LabeledEdit2.Visible:=true;
form2.LabeledEdit3.Visible:=true;
form2.LabeledEdit4.Visible:=true;
form2.LabeledEdit5.Visible:=false;
form2.LabeledEdit6.Visible:=false;
vPanIm:=TLine.Create(form2,clRed);
end;
end.
Выбор примитива
Создаваемый нами код должен отображать один из трех примитивов. За выбор прмитива отвечает оператор case:
procedure TForm2.Button1Click(Sender: TObject);
var i:integer;
begin
i:=form2.RadioGroup1.ItemIndex;
case i of
0: prLine;
1: prTriangl;
2: prCircle ;
end;
end;
В зависимости от выбора кнопки значение I равно 0 для «прямая линия», 1 для треугольник, 2 для окружность.
Оператор выбора case вызовет процедуру prLine при i=0, prTriangl при i=1 и prCircle при i=2.
В программе используется новый компонент для ввода текста — поле ввода с заголовком (меткой) — TLabeledEdit.
if form2.LabeledEdit1.Text<>» then x_b:=strToInt(form2.LabeledEdit1.Text) else
begin
showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’);
exit;
end;
В данном коде проверяется, не пустое ли поле ввода form2.LabeledEdit1.Text<>». Если не пустое, то производится присваивание x_b:=strToInt(form2.LabeledEdit1.Text).
Иначе выводится сообщение showMessage(‘ВВЕДИТЕ ЗНАЧЕНИЕ’); и осуществляется выход из процедуры exit;
Создание объекта примитива
Чтобы примитив был отрисован, необходимо для начала создать объект, соответствующий этому примитиву.
Используем событие RadioGroup1Click:
procedure TForm2.RadioGroup1Click(Sender: TObject);
var i:integer;
begin
if not(vPanIm =nil) then
begin
vPanIm.Free;//или FreeAndNil
vPanIm:=nil;
end;
Производится проверка переменной на НЕравенство значению nil.
Замечание. Если значение не равно nil, то проверка условия (vPanIm =nil) даст значение false. Оператор not инвертирует это значение на true, поэтому будет выполнен код операторв if между begin и end.
Так как проверка показала, что в переменной содержится активная ссылка, то это значит, что существует объект, на который ссылается переменная. Уничтожаем этот объект vPanIm.Free; и присваиваем «пустую» ссылку vPanIm:=nil;
Замечание. Это же можно сделать одной процедурой FreeAndNil.
Формирование интерфейса для выбранного примитива
Далее в зависимости от значения радиогруппы, формируем интерфейс для примитива. Например, для линии:
case i of
0: begin
vPanIm:=TLine.Create(form2,clGreen);
form2.LabeledEdit1.EditLabel.Caption:=’X начало’;
form2.LabeledEdit2.EditLabel.Caption:=’Y начало’;
form2.LabeledEdit3.EditLabel.Caption:=’X конец’;
form2.LabeledEdit4.EditLabel.Caption:=’Y конец’;
form2.LabeledEdit1.Visible:=true;
form2.LabeledEdit2.Visible:=true;
form2.LabeledEdit3.Visible:=true;
form2.LabeledEdit4.Visible:=true;
form2.LabeledEdit5.Visible:=false;
form2.LabeledEdit6.Visible:=false;
end;
Так как благодаря предыдущей проверке переменная vPanIm гарантированно содержит nil, создаём нужный нам объект (в данном случае объект для отрисовки примитива «линия»).
Замечание. Если бы в переменной содержалась ссылка на объект, а мы бы создали новый, то мы записали бы ссылку на новый объектв в переменную.
Тогда ссылка на старый объект была бы потеряна, но сам объект продолжил своё существование в памяти. Доступ к такому объекту теряется навсегда и происходит утечка памяти.
Создание объекта происходит в виде:
vPanIm:=TLine.Create(form2,clGreen);
Ещё раз обратим внимание на ключевой нюанс. Переменная vPanIm имеет тип
TpanelImage. А мы помещаем в vPanIm ссылку на объект типа Tline. Класс Tline есть последователь класса TpanelImage, поэтому такое присваивание допустимо. Тем не менее, такое присваивание накладывает свои ограничения.
Через переменную vPanIm можно обратиться только к полям, свойствам и методам, заложенным в классе TpanelImage, а также к методам его предков.
К полям, свойствам и методам класса-потомка обратиться нельзя (кроме виртуальных методов).
Однако процедура TLine.Create(form2,clGreen); класс TLine формирует полностью и имеется возможность обратиться к «невидимой» части объекта-наследника через переменную vPanIm.
Для этого существует специальный оператор «as».
Например, к полям x_from,y_from,x_to,y_to класса Tline обратиться через переменную vPanIm нельзя.
Но конструкция (vPanIm as TLine).x_from:=x_b; позволяет записать (или прочитать) значение в поле класса-потомка. Говорят, что производится «приведение» базового класса к типу класса-потомка.
Задание начальных значений для примитивов
Таким образом, для построения конкретного примитива необходимо задать для него значения координат (это сделано с помощью LabeledEdit1) и передать их в объект.
(vPanIm as TLine).x_from:=x_b; (vPanIm as TLine).y_from:=y_b;
(vPanIm as TLine).x_to:=x_e; (vPanIm as TLine).y_to:=y_e;
Далее через переменную базового класса вызывается виртуальный метод класса-потмка.
vPanIm.drow;
Динамическое формирование интерфейса

Образцы отрисовки всех трёх примитивов:
Прямая линия:

Треугольник:

Окружность:
