
- •Программирование в Delphi 5
- •Глава 1
- •Основные понятия
- •Создание и уничтожение объектов
- •Инкапсуляция. Свойства
- •Наследование
- •Перегрузка методов
- •Абстрактные методы
- •События и делегирование
- •If Sender is tMenuItem then ShowMessage ('Выбран пункт меню');
- •Обработка сообщений Windows
- •Области видимости
- •Как устроен объект изнутри
События и делегирование
Программистам, давно работающим в Windows, наверное, не нужно пояснять смысл термина "событие". Сама эта среда и написанные для нее программы управляются событиями, возникающими в результате воздействий пользователя, аппаратуры компьютера или других программ. Весточка о наступлении события — это сообщение Windows, полученное так называемой функцией окна. Таких сообщений сотни, и, по большому счету, написать программу для Windows — значит, определить и описать реакцию на некоторые из них.
Работать с таким количеством сообщении, даже имея под рукой справочник. нелегко. Поэтому одним из больших преимуществ Delphi является то, что программист избавлен от необходимостиработать с сообщениями Windows (хотя такая возможность у него есть; об этом — в следующем разделе. Типовых событий в Delphi — не более двух десятков, и все они имеют простую интерпретацию, не требующую глубоких знаний среды.
Рассмотрим, как реализованы события на уровне языка Object Pascal. События — это свойства процедурного типа, предназначенные для создания пользовательской реакции на те или иные входные воздействия:
property OnMyEvent: TMyEvent read FOnMyEvent write FOnMyEvent;
Здесь FOnMyEvent — поле объекта, содержащее адрес некоторого метода. Присвоить такому свойству значение — значит указать объекту адрес метода. который будет вызываться в момент наступления события. Такие методы назовем обработчиками событий. Например, запись:
Application.OnActivate := MyActivatingMethod;
означает, что при активизации объекта Application (так называется объект, соответствующий работающему приложению) будет вызван метод-обработчик MyActivatingMethod.
Внутри библиотеки времени выполнения Delphi вызовы обработчиков событий находятся в методах, обрабатывающих сообщения Windows. Выполнив необходимые действия, этот метод проверяет, известен ли адрес обработчика, и, если это так, вызывает его:
if Assigned(FOnMyEvent) then FOnMyEvent(Self);
События имеют разное количество и тип параметров в зависимости от происхождения и предназначения. Общим для всех является параметр sender — он указывает на объект-источник события. Самый простой тип — TNotifyEvent - не имеет других параметров:
TNotifyEvent = procedure (Sender: TObject) of object;
Тип метода, предназначенный для извещения о нажатии клавиши, предусматривает передачу программисту кода этой клавиши, о передвижении мыши — ее текущих координат и т. п. Все события в Delphi принято предварять префиксом on: oncreate, OnMouseMove, Onpaint и так далее. Щелкнув в Инспекторе объектов на странице Events в поле любого события, вы получите в программе заготовку метода нужного типа. При этом его имя будет состоять из имени текущего компонента и имени события (без префикса On), а относиться он будет к текущей форме. Пусть, например, на форме Formi есть текст Label1. Тогда для обработки щелчка мышью (событие OnClick) будет создана заготовка метода TFormi.
Labeliciick:
procedure Tform1.Label1Click(Sender: TObject);
begin
end;
Поскольку события — это свойства объекта, их значения, можно изменять в любой момент во время выполнения программы. Эта замечательная возможность называется делегированием. Можно в любой момент взять способы реакции на события у одного объекта и присвоить (делегировать) их другому:
Object1.OnMouseMove := Object2.OnMouseMove;
Принцип делегирования позволяет избежать трудоемкого процесса порождения новых дочерних классов для каждого специфического случая, заменяя его простой подстановкой процедур.
Можно при необходимости выбирать один из нескольких возможных вариантов обработчиков событий. В предлагаемом примере мышь задействуется для трех совершенно разных операций: поэтому в зависимости от выбора пользователя для обработки событий от нее задействуется одна из трех групп методов. Большинство компонентов отслеживает три основных события, связанных с мышью — нажатие кнопки (onMouseDown), ее отпускание (onMouseUp) и перемещение мыши (onMouseMove).
В Delphi 5 имеются также события onMouseWheel, OnMouseWheeiup, OnMouseWheelDown. Они предназначены для обработки событий от колесика, имеющегося у таких мышей, как Microsoft IntelliMouse или Genius NetMouse Pro.
Рассмотрим обработку этих событий применительно к изображению (компонент Timage) для его ручной прокрутки (группа методов, имена которых начинаются со слова scroll), измерения длин (Measure) и выделения прямоугольников (select). Таким образом, методов всего девять. Переключение между ними происходит при нажатии соответствующих кнопок (еще три метода). Текущее состояние мыши хранится в переменной FMouseState.
type TMouseState = (msNormal, msDragging, msMeasuring, msSelecting) ;
var
FMouseState : TMouseState;
OldPos, NewPos : TPoint;
MaxShift : TPoint;
procedure TForml.ScrollMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integers;
begin
with Imagel do
begin MaxShift.X := Parent.Width - Width;
MaxShift.Y := Parent.Height - Height;
end;
if (MaxShift.X > 0) and (MaxShift.Y > 0) then Exit;
if MaxShift.X > 0 then MaxShift.X := 0;
if MaxShift.Y > 0 then MaxShift.Y := 0;
FMouseState := msDragging;
OldPos := Point(X, Y);
Screen.Cursor := crHandPoint;
end;
procedure TForml.ScrollMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if FMouseState = msDragging then with Sender as Timage do
begin
NewPos := Point(X - OldPos.X, Y - OldPos.Y);
if Left + NewPos.X > 0 then NewPos.X := - Left;
if Left + NewPos.X < MaxShift.X then NewPos.X := MaxShift.X - Left;
if Top + NewPos.Y > 0 then NewPos.Y := — Top;
if Top + NewPos.Y < MaxShift.Y then NewPos.Y := MaxShift.Y - Top;
Parent.ScrollBy(NewPos.X, NewPos.Y) ;
end;
end;
procedure TForml.ScrollMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin FMouseState := msNormal;
Screen.Cursor := crDefault;
with Sender as Timage do Labell.Caption := Format('(%d , %d)',[-Left, -Top]) ;
end;
procedure TForml.MeasureMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
FMouseState := msMeasuring;
OldPos := Point(X, Y);
NewPos := OldPos;
Imagel.Canvas.Pen.Mode := pnNotXor;
Imagel.Canvas.Pen.Color := cIRed;
Imagel.Canvas .MoveTo (X, Y);
Screen.Cursor := crCross;
end;
procedure TFormI.MeasureMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
begin
if FMouseState = msMeasuring then begin Imagel. Canvas. Poly-Line ([OldPos, NewPos]);
NewPos := Point(X, Y);
Iinagel.Canvas.PolyLine([OldPos, NewPos]);
Labell.Caption := Format('%7.2f,
[Sqrt(Sqr(Longint(X - OldPos-X))
+Sqr(Longint(Y - OldPos. Y) ) ) ]);
end;
end;
procedure TFormI.MeasureMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integers); begin
Imagel.Canvas.PolyLine([OldPos, NewPos]) ;
Screen.Cursor := crDefault;
FMouseState := msNormal;
end;
procedure TFormI.SelectMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integers); begin FMouseState := msSelecting;
OldPos := Point(X, Y);
NewPos := OldPos;
with Imagel. Canvas do
begin
Pen.Mode := pmNotXor;
Pen. Color := crGreen;
MoveTo(X,Y) ;
end;
Screen. Cursor := crCross;
procedure TPormI.SelectMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if FMouseState = msSelecting then begin
Imagel.Canvas.Rectangle(OldPos.X, OldPos.Y, NewPos.X, NewPos.Y);
NewPos := Point(X, Y) ;
Imagel.Canvas.Rectangle(OldPos.X, OldPos.Y, NewPos.X, NewPos.Y);
Labell.Caption := Format('%d x %d',[X - OldPos.X, Y - OldPos.Y]);
end;
end;
procedure TFormI.SelectMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y; Integers-begin
Imagel.Canvas.Rectangle(OldPos.X,
OldPos.Y, NewPos.X, NewPos.Y);
Screen.Cursor := crDefault;
FMouseState := msNormal;
end;
procedure TFormI.SelectButtonClick
(Sender: TObject);
begin with Imagel do begin
OnMouseDown := SelectMouseDown;
OnMouseMove := SelectMouseMove;
OnMouseUp := SelectMouseUp;
Cursor := crCross;
end;
end;
procedure TFoml.
MeasureButtonClick( Sender: TObject);
begin with Imagel do
begin
OnMouseDown := MeasureMouseDown;
OnMouseMove := MeasureMouseMove;
OnMouseUp := MeasureMouseUp;
Cursor := crCross;
end;
end;
procedure TFormI.ScrollButtonClick(Sender: TObject);
begin with Image 1 do begin OnMouseDown := ScrollMouseDown;
OnMouseMove := ScrollMouseMove;
OnMouseUp := ScrollMouseUp;
Cursor := crDefault;
end;
end;
Но какой механизм позволяет подменять обработчики, ведь это не просто процедуры, а методы? Здесь как нельзя кстати приходится существующее в Object Pascal понятие указателя на метод.Отличие метода от обычной процедуры состоит в том, что помимо явно описанных параметров методу всегда неявно передается еще и указатель на вызвавший его экземпляр (переменная Self). Вы можете описать процедурный тип, который будет совместим по присваиванию с методом (то есть предусматривать получение self). Для этого в описание процедуры нужно добавить зарезервированные слова of object. Указатель на метод — это указатель на такую процедуру.
type TMyEvent = procedure(Sender:
TObject; var AValue: Integer) of object;
TIstObject = class;
FOnMyEvent: TMyEvent;
property OnMyEvent: TMyEvent read
FOnMyEvent write FOnMyEvent;
end;
T2nd0bject = class;
procedure SetValuel(Sender: TObject; var AValue: Integers); procedure SetValue2(Sender: TObject; var AValue: Integer);
end;
var
Obj1: TIstObject;
Obj2: T2nd0bject;
begin
Obj1 := TIstObject.Create ;
Obj2 := T2nd0bject.Create;
Obj1.OnMyEvent := Obj2.SetValuel;
Obj2.OnMyEvent := Obj2.SetValue2;
...
end.
Этот пример показывает, что при делегировании можно присваивать методы других классов. Здесь обработчиком события onMyEvent объекта objl по очереди выступают методы setvaluel и SetValue2 объекта obj2.
Однако здесь имеется один подводный камень. Даже подмена в предыдущем примере трех событии от мыши тремя группами обработчиков породила девять методов, занявших не одну страницу текста. Эти методы относятся к главной форме (объекту TFormi), которая и без того бывает загромождена большим количеством обработчиков других событий. Возникает естественное желание обособить эти девять методов, выделить их. К сожалению, их нельзя сделать просто процедурами — обработчики событий обязательно должны быть чьими-то методами. Но их можно "отдать" какому-либо другому объекту. Более того, можно описать и создать специальный объект (назовем его, к примеру, TMouseEventCarrier). Его единственное предназначение — быть носителем методов, которые затем делегируются другим объектам. Разумеется, такой объект надо не забыть создать до использования его методов, а в конце — уничтожить. Можно и не делать этого, объявив методы методами класса, о которых — в одном из последующих разделов.
Мы сейчас решили задачу использования нескольких разных обработчиков того или иного события для одного объекта. Но не менее часто требуется решить обратную задачу — а как использовать для разных событий разных объектов один и тот же обработчик?
Если никакой "персонификации" объекта, вызвавшего метод, не нужно, все делается тривиально и никакой проблемы не возникает. Самый простой при- , мер: в современных программах основные функции дублируются дважды — в меню и в панели инструментов. Естественно, сначала нужно создать и наполнить метод содержимым (скажем, для пункта меню), а затем в Инспекторе объектов указать его же для кнопки панели инструментов.
Более сложен случай, когда внутри такого метода нужно разобраться, кто собственно его вызвал. Если потенциальные кандидаты имеют разный объектный тип (как в предыдущем абзаце — кнопка и пункт меню), то именно объектный тип можно применить в качестве критерия :