Перекрытие виртуальных методов

Объекты. Занятие 2_7. Часть 7.

Очертим поставленную задачу. Мы собираемся создать подобие графического редактора, способного отображать линии, треугольники и окружности.

В итоге должен получиться следующий интерфейс.

delphi работа с графикой

На компоненте Tpanel (красный фон) размещён компонент Timage (сиреневый фон).

Delphi работа с графикой

Для каждого типа геометрической фигуры будет задан свой набор координат, по которым она будет отстраиваться.

Для линии — это координаты начала и конца отрезка.

Для треугольника — это координаты 3- его вершин.

Для окружности — это координата центра и радиус.

Canvas delphi

Для решения этих задач воспользуемся тремя стандартными геометрическими функциями, к которым можно обратиться из компонента Image, а точнее — с помощью его свойства canvas:

moveTo(координата точки) — переместить «перо» в точку «координата точки».

lineTo(координата точки) – нарисовать прямую линию от текущего положения «пера» до точки «координата точки».

ellipse(координата точки один, координата точки два) — нарисовать эллипс. Эллипс рисуется так, чтобы быть вписанным в прямоугольник. У этого прямоугольника первая координата — правый верхний угол, вторая — левый нижний угол.

Напишем методы, которые будут отображать эти геометрические фигуры.

procedure TLine.draw(vComp:TWinControl);
begin
vIm.Canvas.MoveTo(x_from,y_from);//переместить перо в точку с координатами
vIm.Canvas.LineTo(x_to,y_to);//провести линию к указанной точке от текущего положения
end;

procedure TTriangle.draw(vComp:TWinControl);
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.draw(vComp:TWinControl);
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;

За отрисовку прямых линий будет отвечать класс TLine. За отрисовку треугольников будет отвечать класс TTriangle. За отрисовку окружности (как частного случая эллипса) будет отвечать класс TCircle.

Эти классы будут отвечать за предоставление процедурам MoveTo, LineTo и Ellipse значений координат.

Классы для отображения примитивов

Соответственно, классы будут иметь вид:

TLine= class(TPanelImage)
public
x_from,y_from,x_to,y_to:integer;
constructor create(vComp:TWinControl;Color:TColor);
procedure draw;override;
end;

Здесь vComp — ссылка на компонент, который будет владельцем. Color — цвет

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 draw;override;
end;

TCircle=class(TPanelImage)
public
x_from,y_from,R:integer;
constructor create(vComp:TWinControl;Color:TColor);
procedure draw;override;
end;

Все три класса являются наследниками базового для них класса TPanelImage (о нём дальше).

Каждый класс имеет поля данных, необходимые для отрисовки объекта:
1) x_from,y_from,x_to,y_to:integer; — координаты начальной и конечной точек линии;
2) x_1,y_1:integer; x_2,y_2:integer; x_3,y_3:integer; координаты вершин треугольника
3) x_from,y_from,R:integer; координата центра и радиус окружности.

delphi работа с графикой

В Delphi изображения строятся в указанной на рисунке системе координат x0y.

Для отрисовки окружности надо задать координаты x1,y1 и x2,y2. Однако гораздо привычнее задавать координату центра окружности x_from, y_from и её радиус R.

Очевидно, что эллипс превращается в окружность, когда х1-х2=y2-y1 (то есть окружность вписывается в квадрат).

Из рисунка видно, что x1= x_from+R, y1= y_from-R, x2= x_from-R, y2=y_from+R.

Далее следуют constructor create(vComp:TWinControl;Color:TColor); и метод procedure draw;override;

Конструкторы классов

Реализация конструкторов для классов следующая:

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;

Разберём, например, конструктор TLine.create.

Первой строкой идёт вызов конструктора create(vComp); с ключевым словом inherited.

Такая конструкция означает, что вызывается конструктор класса, от которого порождён класс TCircle (конструктор класса-предка). Реализацию этого конструктора мы посмотрим далее.

Сейчас заметим, что в конструкторе класса-предка заложено создание объекта «панель» с именем vPan. Для созданного объекта vPan устанавливаем цвет через свойство vPan.Color.

Базовый класс — предок

В качестве предка используем класс:

TPanelImage=class(TPanel)
private
vIm:TImage;
public
vPan:TPanel;
constructor create(vComp:TWinControl);
procedure draw;virtual; abstract;
end;

Как мы видели ранее, все три класса TLine, TTriangle и TCircle унаследованы от класса TPanelImage.

Рассмотрим его реализацию.

Поле vIm:TImage; хранит ссылку на объект типа

В Delphi изображения строятся в указанной на рисунке системе координат x0y.
Для отрисовки окружности надо задать координаты x1,y1 и x2,y2. Однако гораздо привычнее задавать координату центра окружности x_from, y_from и её радиус R.
Очевидно, что эллипс превращается в окружность, когда х1-х2=y2-y1 (то есть окружность вписывается в квадрат).
Из рисунка видно, что x1= x_from+R, y1= y_from-R, x2= x_from-R, y2=y_from+R.
Далее следуют constructor create(vComp:TWinControl;Color:TColor); и метод procedure draw;override;
Реализация конструкторов для классов следующая:

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;


Разберём, например, конструктор TLine.create.
Первой строкой идёт вызов конструктора create(vComp); с ключевым словом inherited.

Такая конструкция означает, что вызывается конструктор класса, от которого порождён класс TCircle (конструктор класса-предка). Реализацию этого конструктора мы посмотрим далее.
Сейчас заметим, что в конструкторе класса-предка заложено создание объекта «панель» с именем vPan. Для созданного объекта vPan устанавливаем цвет через свойство vPan.Color.
В качестве предка используем класс:

TPanelImage=class(TPanel)
private
vIm:TImage;
public
vPan:TPanel;
constructor create(vComp:TWinControl);
procedure draw;virtual; abstract;
end;

Как мы видели ранее, все три класса TLine, TTriangle и TCircle унаследованы от класса TPanelImage.
Рассмотрим реализацию класса TPanelImage.

Поле vIm:TImage; хранит ссылку на объект типа Timage. В палитре компонентов ему соответствует компонент Image.
Поле vPan:TPanel; хранит ссылку на объект «панель».

Конструктор constructor create(vComp:TWinControl); реализован следующим образом:

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);// delphi прямоугольник
vIm.Canvas.Pixels[0,0]:=clRed;// delphi pixel — нарисовать точку (пиксел)
end;

Рассмотрим входящие в описание конструктора элементы.
В конструкторе создаём объект (компонент) «панель»:
Vpan:=TPanel.Create(vComp);

Параметр vComp нужен, чтобы задать владельца для компонента. Вспомним, что «владелец» отвечает за уничтожение расположенного на нём компонента при закрытии «владельца».

Если компонент будет расположен на form2, то будет указано значение form2.

Чтобы компонент получал сообщения и отображался на форме, необходимо указать эту форму в качестве «родителя»: Vpan.Parent:=vComp;
Указываем координаты левого верхнего угла, ширину и высоту панели, а также её цвет.

Далее создаём компонент «холст» типа TImage и используем image canvas delphi:

vIm:=TImage.Create(vComp);

Чтобы он был привязан к панели (отображался на панели и перемещался с ней, а также получал через нее сообщения), указываем в качестве родителя созданный компонент «панель» Vpan:

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);

Свойство Canvas delphi

Здесь использовано свойство «холста» Canvas. Это свойство позволяет рисовать на поверхности элемента. Под рисованием понимается как рисование прямых и непрямых линий, так и закрашивание областей «холста».

Как будет закрашен холст, зависит от значения свойства Brush (кисть). Значение bsSolid означает, что заливка будет сплошной.

Brush.Color устанавливает цвет кисти. В данном случае цвет задаётся с помощью функции rgb(220,200,225). В скобках задают три оттенка (красный, зелёный, голубой). Значение каждого оттенка может быть от 0 до 255.

Закрашивать области холста с помощью кисти можно разными способами. Здесь использована заливка цветом прямоугольной области:

vIm.Canvas.Rectangle(0,0,Vpan.Width-20,Vpan.Height-20); (левый верхний угол, ширина, высота). Этот прямоугольник будет закрашен цветом текущей кисти, то есть rgb(220,200,225).

Но холст на панели «проявится» только тогда, когда будет отрисован хоть один пиксел. Поэтому отобразим пиксел красного цвета в левом верхнем углу:
vIm.Canvas.Pixels[0,0]:=clRed;

Завершает построение класса TPanelImage=class(TPanel)
описание вызова процедуры отрисовки объекта (линии, треугольника или окружности):

procedure draw;virtual; abstract;

Абстрактные методы в delphi

Здесь присутствуют два ключевых слова: virtual; и abstract. Если бы слово «abstract» не присутствовало, то надо было бы написать реализацию метода: что-то типа:

procedure TPanelImage.draw;
begin

end;

Однако класс TPanelImage не отвечает за отрисовку конкретных примитивов, а только подготавливает холст для последующего рисования на нём.

Методы draw будут реализованы в классах-потомках, каждый из которых будет отвечать за свой примитив. Директива abstract позволяет не создавать метод для класса TPanelImage.

Определять метод draw надо будет в классах-потомках.
Второе ключевое слово — virtual. Оно означает, что можно создавать в классах-потомках одноимённый метод draw (если бы у метода были параметры, то они в точности должны каждый раз повторяться).

Итак, за отрисовку прямых линий отвечает класс:

TLine= class(TPanelImage)
public
x_from,y_from,x_to,y_to:integer;
constructor create(vComp:TWinControl;Color:TColor);
procedure draw;override;
end;

В нём присутствует указание на процедуру procedure draw;
Объявление процедуры имеет директиву override. Для чего она нужна?

Эта директива работает в связке с директивой virtual.
При компиляции программы компилятор, встретив директиву virtual, создаст так называемую «виртуальную» таблицу, называемую VMT. Если бы метод draw не был объявлен как абстрактный, то ему должен был бы соответствовать код процедуры:

procedure TPanelImage.draw;
begin

end;

При объявлении метода абстрактным такой реализации не требуется.
Непосредственное использование созданных классов и реализация интерфейса программы — в следующей статье.

Обновлено: 06.02.2021 — 22:26

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

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