Реализация интерфейса «имитация графического редактора»

Объекты. Занятие 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;

Динамическое формирование интерфейса

Приведение типов классов

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

Прямая линия:

Приведение типов классов

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

Приведение типов классов

Окружность:

Приведение типов классов

Обновлено: 09.02.2021 — 13:03

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *