События
Но какой механизм позволяет подменять обработчики, ведь это не просто процедуры, а методы? Здесь как нельзя кстати приходится существующее в Object Pascal понятие указателя на метод. Отличие метода от процедуры состоит в том, что помимо явно описанных параметров методу всегда неявно передается еще и указатель на вызвавший его экземпляр класса (переменная self). Вы можете описать процедурный тип, который будет совместим по присваиванию с методом (т. е. предусматривать получение self). Для этого в описание процедуры нужно добавить зарезервированные слова of object. Указатель на метод – это указатель на такую процедуру.
type TMyEvent = procedure(Sender: TObject; var AValue: Integer) of object; TlstObject = class; FOnMyEvent: TMyEvent; property OnMyEvent: TMyEvent read FOnMyEvent write FOnMyEvent; end; T2ndObject = class; procedure SetValuel(Sender: TObject; var AValue: Integer); procedure SetValue2(Sender: TObject; var AValue: Integer); end; … var Objl: TlstObject; Obj2: T2ndObject; begin Objl: = TlstObject.Create; Obj2: = T2ndObject.Create; Obj1.OnMyEvent: = Obj2.SetValuel; Obj1.OnMyEvent: = Obj2.SetValue2; … end.
Этот пример показывает, что при делегировании можно присваивать методы других классов. Здесь обработчиком события OnMyEvent объекта Obj1 по очереди выступают методы SetValue1 и Setvaiue2 объекта Obj2.
Обработчики событий нельзя сделать просто процедурами – они обязательно должны быть чьими-то методами. Но их можно "отдать" какому-либо другому объекту. Более того, для этих целей можно описать и создать специальный объект. Его единственное предназначение – быть носителем методов, которые затем делегируются другим объектам. Разумеется, такой объект надо не забыть создать до использования его методов, а в конце – уничтожить. Можно и не делать этого, объявив методы методами класса, о которых речь пойдет в одном из последующих разделов.
Мы сейчас решили задачу использования нескольких разных обработчиков того или иного события для одного объекта. Но не менее часто требуется решить обратную задачу – а как использовать для различных событий разных объектов один и тот же обработчик?
Если никакой "персонификации" объекта, вызвавшего метод, не нужно, все делается тривиально и проблемы не возникает. Самый простой пример: в современных программах основные функции дублируются дважды – в меню и на панели инструментов. Естественно, сначала нужно создать и наполнить метод содержимым (скажем, для пункта меню), а затем в Инспекторе объектов указать его же для кнопки панели инструментов.
Более сложный случай, когда внутри такого метода нужно разобраться, кто собственно его вызвал. Если потенциальные кандидаты имеют разный объектный тип (как в предыдущем абзаце – кнопка и пункт меню), то именно объектный тип можно применить в качестве критерия:
If Sender is TMenuItem then ShowMessage('Выбран пункт меню');
Если же все объекты, разделяющие между собой один обработчик события, относятся к одному классу, то приходится прибегать к дополнительным ухищрениям. Типовой прием – использовать свойство Tag, которое имеется у всех компонентов, и, вполне вероятно, именно для этого и задумывалось:
const colors: array[0..7] of TColor = clWhite,clRed,clBlue,clYellow,clAqua,clGreen, clMaroon,clBlack); procedure TForml.CheckBoxClick(Sender: TObject); begin with TCheckBox(Sender) do if Checked then Color: = Colors[Tag] else Color: = clBtnFace; end;
Пусть в форме имеется несколько переключателей. Для того чтобы при нажатии каждый из них окрашивался в свой цвет, нужно в Инспекторе объектов присвоить свойству Tag значения от 0 до 7 и для каждого связать событие onclick с методом CheckBoxClick. Этот единственный метод справится с задачей для всех переключателей.