Ракитин Р.Ю. ООП в Turbo Delphi
.PDF171
Назначение обработчиков событий
Вне зависимости от того, каким образом был создан компонент – в визуальном построителе, или во время выполнения программы, он одинаковым образом интегрируется в общую схему работы, в том числе поддерживается и возможность использования событий. Ссылка на обработчик события компонента представляет собой свойство, которое имеет какой-либо процедурный тип данных.
Например, в нашем приложении можно организовать обработку события OnKeyPress компонента Edit1, создаваемого во время выполнения программы. Процедурный тип свойства OnKeyPress описан следующим образом:
туре
TKeyPressEvent = procedure (Sender: TObject; var Key: Char) of Object;
Следовательно, обработчик события должен являться методом какого-либо класса (так как использована конструкция of Object) и иметь два параметра: Sender типа TObject, и параметр-переменную Key типа Char.
Опишем такой метод в классе формы, поместив его заголовок в секцию private интерфейсной части, и реализовав его в описательной части модуля:
TForml |
= class (TForm) |
... |
|
private |
|
Edit1: TEdit;
Procedure EditEvent(Sender: TObject; var Key: Char);
...
Procedure TForml.EditEvent(Sender: TObject; var Key: Char);
begin
if Key = ‘х’ then Key := #0; end;
...
Описанный метод позволяет игнорировать нажатия на клавишу ‘х’. Для
назначения данного обработчика элементу управления следует занести ссылку на метод в свойство OnKeyPress, используя название метода. Сделаем это добавив в обработчик события OnClick кнопки Button1 строку:
Edit1.OnKeyPress:= EditEvent;
172
Глава 19. Определение собственных классов и работа с ними
Как отмечалось во второй главе, Delphi предлагает не только большое количество готовых классов, но и дает программисту обширный инструмент для разработки собственных объектов и классов. Рассмотрим пример создания структуры новых классов средствами языка Object Pascal.
Графический редактор «Окружности».
Пусть требуется разработать приложение, позволяющее рисовать на специальном поле окна окружности. Местоположение центра окружности должно определяться щелчком левой клавиши мыши. Радиус окружности и цвет контура должны настраиваться.
1. Разрабатываемое приложение легко декомпозируется на два объекта: окно приложения и изображаемая в нем окружность. Окно должно содержать интерфейсные элементы, позволяющие выполнять настройку параметров круга и саму процедуру рисования – Edit, UpDown связанный с Edit, Button для выбора цвета и выхода, объект ColorDialog. Оно проектируется на базе класса TForm с использованием стандартных визуальных компонентов.
2. Создадим Класс TMyCircle в соответствии с правилами Delphi он наследуется от TObject. Этот класс добавляет поля для хранения координат центра окружности (х, у), ее радиуса r, цвета Color и имени компонента Image, на холсте которого нарисована окружность. Набор методов в основном определяется перечнем сообщений, на которые должен реагировать объект данного класса. Дополнительно переопределим метод Create, чтобы использовать его для инициализации полей объекта при создании, кроме этого
173
целесообразно определить метод Clear для стирания нарисованной окружности при изменении ее размера или цвета.
3. Определим тип доступа для всех компонентов класса: все поля и метод Clear, к которому не требуется доступ извне, объявим в секции private (внутренние), а все остальные компоненты – в секции public.
4. Объявление класса TMyCircle выполним в отдельном модуле Circul:
unit Circul; |
|
|
interface |
|
|
Uses Extctrls, Graphics; |
|
|
type |
|
|
TMyCircle=class |
|
|
private |
|
|
x, y, r: Word; |
//координаты центра и радиус окружности |
|
Color: TColor; |
//цвет |
|
Image:TImage; |
//поле для рисования |
|
procedure Clear; //стирание окружности |
||
public |
|
|
constructor Create(almage: TImage; ax, ay, ar:Word; |
||
aColor:TColor); |
//конструктор |
|
procedure Draw; |
//рисование |
|
procedure ReSize(ar: Word); //изменение размеров |
||
procedure ReColor(aColor: TColor); //изменение цвета |
||
end; |
|
|
implementation |
|
|
constructor TMyCircle.Create; |
||
begin |
|
|
inherited Create; |
//вызвать наследуемый конструктор |
|
Image:= aImage; |
//инициализировать поле |
|
x:=ax; |
|
|
y:=ay; |
|
|
r:=ar;
Color:= aColor; end;
procedure TMyCircle.Draw; begin
{задать цвет пера} Image.Canvas.Pen.Color:= Color; {нарисовать окружность} Image.Canvas.Ellipse(x-r, y-r, x+r, y+r);
end;
174
procedure TMyCircle.Clear; var
TempColor:TColor; begin
TempColor:= Color; //сохранить цвет пера
//фиксировать цвет фона
Color:= Image.Canvas.Brush.Color;
Draw; //нарисовать цветом фона – стереть
Color:= TempColor; //востановить цвет пера end;
procedure TMyCircle.ReSize; begin
Clear;
r:=ar;
Draw; end;
procedure TMyCircle.ReColor(aColor: TColor); begin
Clear;
Color:= aColor; Draw;
end; End.
5. Теперь переходим к программированию обработчиков событий, при наступлении которых должны выполняться основные операции приложения. Ниже приводится часть текста модуля Main, соответствующего форме
TMainForm. unit Unit1;
interface
...
implementation
{$R *.dfm} uses Circul; var
C:TMyCircle; //объявляем объект класса TMyCircle
{обработчик выбора цвета}
procedure TForm1.ButtonColorClick(Sender: TObject); begin
{если выполнен диалог выбора цвета, то} if ColorDialog1.Execute then
{установить цвет} Image1.Canvas.Pen.Color:=ColorDialog1.Color;
175
{если объект создан, то} if C<>nil then
{перерисовать другим цветом} C.ReColor(Image1.Canvas.Pen.Color);
end;
{обработчик кнопки Выход}
procedure TForm1.ButtonExitClick(Sender: TObject); begin
Application.Terminate; end;
{выполняется при активации формы}
procedure TForm1.FormActivate(Sender: TObject); begin
{установить белый фон} Image1.Canvas.Brush.Color:= clWhite; {установить цвет рисования} Image1.Canvas.Pen.Color:= clBlack;
end;
{обработчик нажатия мышью на объекте Image} procedure TForm1.Image1MouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
{если нажата левая клавиша мыши} if Button=mbLeft then
begin
{если объект создан, то уничтожить его} C.Free;
{конструировать}
C:= TMyCircle.Create(Image1, X, Y, StrToInt(EditR.Text), Image1.Canvas.Pen.Color);
{изобразить объект с заданными параметрами} C.Draw;
end; end;
{изменение радиуса круга}
procedure TForm1.UpDown1Click(Sender: TObject; Button: TUDBtnType);
begin
{если объект создан, то перерисовать с другим радиусом } if C<>nil then C.ReSize(StrToInt(EditR.Text));
end;
176
initialization
{Последний используемый объект уничтожается при закрытии формы}
finalization
C.Free; end.
Из приведенного текста видно, что конкретный объект класса TMyCircle создается при нажатии левой клавиши мыши. Если объект уже существовал, то он уничтожается. Соответствующая уничтоженному объекту окружность на холсте сохраняется, но теряется связь изображения и объекта. После этого мы больше не можем менять цвет и размер этой окружности. Таким образом, одна и та же переменная
используется многократно для хранения адресов различных объектов при рисовании
6.Добавьте возможность изменять толщину и тип линии окружности.
Использование абстрактных методов (графический редактор «Окружности и квадраты»).
Изменим пример предыдущего раздела так, чтобы можно было рисовать не только окружности, но и, например, квадраты.
Естественно, теперь интерфейс должен обеспечивать возможность выбора фигуры. С этой целью добавим в форму компонент TRadioGroup. При обработке события нажатия мышью на объекте Image необходимо учитывать тип выбранной фигуры.
Класс TMySquare можно построить несколькими способами:
1.Класс ТМуSquare можно наследовать от TObject так же, как был получен класс ТМуСirсlе. В этом случае нам придется повторить программирование всех методов данного класса, что явно нецелесообразно.
2.Класс TMySquare можно наследовать от TMyCircle. Тогда в дочернем классе будут доступны методы TMyCircle Create, Clear, ReColor, ReSize,
определяющие общие элементы поведения. Метод Draw необходимо объявить виртуальным, так как он вызывается из наследуемых методов и переопределен в классе-потомке. Метод Draw класса TMySquare должен быть объявлен переопределенным – override. Данный вариант наследования, принципиально применимый в конкретном случае, является неуниверсальным (и, в общем, нелогичным: как можно «наследовать» от круга квадрат?).
3.С учетом существования двух типов объектов со сходным поведением можно создать абстрактный класс TFigure, инкапсулирующий требуемый тип
поведения фигур. В этом классе нужно объявить метод Draw виртуальным (virtual) и абстрактным (abstract) и определить методы Create, Clear, ReColor, ReSize через метод Draw. Теперь мы можем наследовать от этого
177
абстрактного класса классы, рисующие любые фигуры. Эти классы должны будут переопределять абстрактный метод Draw класса TMyFigure.
Ниже представлен текст модуля Figure. unit Figure;
interface
uses Extctrls, Graphics;
type
TMyFigure=class private
x, y, r:Word; Color: TColor; Image :TImage; procedure Clear;
public
constructor Create(almage: TImage; ax, ay, ar: Word; aColor:TColor);
{абстрактная процедура}
procedure Draw; virtual; abstract; procedure ReSize(ar: Word); procedure ReColor(aColor:TColor);
end;
{класс Окружность} TMyCircle=class(TMyFigure) public
procedure Draw; override; //рисование окружности end;
{класс Квадрат} TMySquare=class (TMyFigure) public
procedure Draw; override; //рисование квадрата end;
implementation
constructor TMyFigure.Create; begin
inherited Create; Image:=aImage; x:=ax;
y:=ay;
r:=ar;
Color:=aColor; end;
178
procedure TMyFigure.Clear; var
TempColor:TColor; begin
TempColor:= Color;
Color:= Image.Canvas.Brush.Color;
Draw; {нарисовать фигуру цветом фона - стереть} Color:=TempColor;
end;
procedure TMyFigure.Resize; begin
Clear;
r:=ar;
Draw; end;
procedure TMyFigure.Recolor; begin
Clear;
Color:= aColor; Draw;
end;
procedure TMyCircle.Draw; begin
Image.Canvas.Pen. Color:=Color; Image.Canvas.Ellipse(x-r, y-r, x+r, y+r);
end;
procedure TMySquare.Draw; begin
Image.Canvas.Pen.Color:=Color; Image.Canvas.Rectangle(x-r, y-r, x+r, y+r);
end; End.
Задания для самостоятельного выполнения:
1.Создайте приложение с использованием модуля Figure.
2.Добавьте возможность изменять толщину и тип рисуемой линии фигуры.
3.Добавьте возможность в приложении рисовать треугольник, звезду (для этого создайте соответствующие классы).
179
Глава 20. Многопоточность
Понятие и назначение потоков
Поток – это последовательность каких-либо команд и вызовов подпрограмм. В каждой программе, таким образом, имеется как минимум один поток, называемый основным, однако могут быть созданы и дополнительные потоки, выполняемые параллельно основному.
Потоки внутри программы работают совместно, конкурируя между собой в отношении процессорного времени. Какому потоку следует передать управления за счет другого потока, определяет операционная система на основе важной характеристики потока – его приоритета.
Даже если поток выполняет бесконечный цикл, он не может «подвесить» систему или снизить скорость ее работы. Еще один интересный нюанс многопоточных приложений состоит в том, что при некоторых долгосрочных операциях не используются ресурсы процессора. Такой операцией, например, является запись информации на диск.
Поток выполнения является системным понятием и поддерживается непосредственно средствами Windows. В Delphi свойства и методы потоков инкапсулированы в класс TThread (англ. Thread нить, поток), в котором заявлен абстрактней метод Execute без параметров, как раз и представляющий собой последовательность команд, выполняемых в потоке:
procedure Execute; virtual; abstract;
Для создания дополнительного потока в программе следует реализовать класс-наследник TThread, переопределив метод Execute.
В среде Delphi поток можно реализовать в виде отдельного модуля. Для этого необходимо выбрать в меню File → New → Other → Thread Object. После
нажатия на ярлыке появиться окно, в котором указывается имя нового класса:
180
После этого будет создан новый модуль, содержащий следующий код: unit Unit1;
interface
uses
Classes;
type
TCalcThread = class(TThread) private
{ Private declarations } protected
procedure Execute; override; end;
implementation
{ Важно: Методы и свойства визуальных объектов могут быть вызваны только в методе Synchronize, например,
Synchronize(UpdateCaption);
иUpdateCaption должен быть реализован по примеру,
procedure TCalc.UpdateCaption; begin
Form1.Caption := 'Обновление в потоке'; end; }
{TCalcThread }
procedure TCalcThread.Execute; begin
{ Поместите сюда код потока } end;
end.
По ходу модуля Delphi вставляет необходимые комментарии.
Для использования созданного модуля необходимо подключить его в основном модуле.
Описание потока не приводит к выполнению каких-либо действий. Для того чтобы потоку было передано управление, должен быть создай экземпляр потокового класса, заполнены внутренние свойства, необходимые для выполнения вычислений, после чего поток запускается. Например: