Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программирование в Delphi 5.doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
410.11 Кб
Скачать

События и делегирование

Программистам, давно работающим в 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). Его единственное предназначение — быть носителем методов, которые затем делегируются другим объектам. Разумеется, такой объект надо не забыть создать до использования его методов, а в конце — уничтожить. Можно и не делать этого, объявив методы методами класса, о которых — в одном из последующих разделов.

Мы сейчас решили задачу использования нескольких разных обработчиков того или иного события для одного объекта. Но не менее часто требуется решить обратную задачу — а как использовать для разных событий разных объектов один и тот же обработчик?

Если никакой "персонификации" объекта, вызвавшего метод, не нужно, все делается тривиально и никакой проблемы не возникает. Самый простой при- , мер: в современных программах основные функции дублируются дважды — в меню и в панели инструментов. Естественно, сначала нужно создать и наполнить метод содержимым (скажем, для пункта меню), а затем в Инспекторе объектов указать его же для кнопки панели инструментов.

Более сложен случай, когда внутри такого метода нужно разобраться, кто собственно его вызвал. Если потенциальные кандидаты имеют разный объектный тип (как в предыдущем абзаце — кнопка и пункт меню), то именно объектный тип можно применить в качестве критерия :