Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ВВЕДЕНИЕ В ОБЪЕКТНО Ориентированное программиро...docx
Скачиваний:
19
Добавлен:
29.08.2019
Размер:
1.01 Mб
Скачать

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

Объекты в программе должны взаимодействовать между собой. Кроме этого, они должны взаимодействовать с внешним миром — с пользователем через операционную систему. Когда они взаимодействуют между собой, это обычно осуществляется через вызов методов. Для организации взаимодействие с Windows в Delphi может использоваться механизм событий.

События - это свойства процедурного типа, предназначенные для создания поведения объекта в ответ на входные воздействия:

Property OnMyEvent: TMyEvent read FOnMyEvent write FOnMyEvent

Здесь FOnMyEvent - поле объекта, содержащее адрес некоторого метода. Примеры таких свойств можно обнаружить на закладке Events инспектора объектов Delphi, это такие события как onclick, OnMouseMove и др.

Присвоить такому свойству значение - значит указать объекту адрес метода, который будет вызываться в момент наступления события. Такие методы называют обработчиками событий. Вызов обработчиков событий в Delphi обычно скрыт от программиста. Механизм вызова будет рассмотрен позднее. События имеют разное количество и тип параметров в зависимости от происхождения и предназначения. Общим для всех является параметр SendenTObject - он указывает на объект-источник события. В простейшем случае обработчик события может не иметь других параметров.

Поскольку события — это свойства объектов, их значения можно изменять в любой момент выполнения программы. Эту возможность называют делегированием. Делегирование - это механизм, позволяющий во время выполнения программы брать способы реакции на события у одного объекта и присваивать (делегировать их другому). Объекты характеризуются состоянием и поведением. Состояния - поля, их набор постоянен, значения изменяются. Поведение - методы. Во всех рассмотренных ранее случаях поведение объекта не изменяется во время выполнения программы. Делегирование дает возможность изменить поведение объекта во время выполнения программы и, вообще, может использоваться более широко, чем только для обработки событий. Принцип делегирования позволяет избежать трудоемкого процесса порождения новых дочерних классов для каждого специфического случая, заменяя его простой подстановкой процедур. Можно при необходимости иметь несколько возможных вариантов обработчиков событий и менять их в процессе выполнения программы. Рассмотрим механизм, позволяющий описывать обработчики вне класса. Для начала рассмотрим механизм указателей на функции. В Object Pascal есть тип данных, называемый процедурным. Переменная этого типа может содержать указатель на процедуру или функцию. Процедурный тип позволяет, в частности, передавать указатель на процедуру как параметр в другую процедуру. Пример использования - процедура сортировки массива, применимая для массивов с разными типами элементов. В эту процедуру должен передаваться указатель на процедуру сравнения двух элементов массива и процедуру обмена двух элементов массива. Пример:

function CaIc{X,Y: Integer): Integer; - функция

var F: function (X,Y: Integer): Integer; - указатель на функцию того же типа

F := Calc;

A:=F(10,20);

A:=Calc{10,20);

Две последние строки содержат вызов функции Calc в первом случае через указатель на нее, во втором через ее имя. Причем, как и для переменных типа указатель на класс, т.е. объект, для типа указатель на процедуру специальных обозначений, показывающих что это указатель не используется. Можно передавать переменные процедурного типа в качестве параметров процедуры:

Два процедурных типа совместимы, если описываемые ими процедуры или функции имеют равное число параметров и типы соответствующих параметров совпадают (имена параметров не имеют значения), если функции возвращают значения одного типа. Вложенные процедуры и функции нельзя использовать в качестве значений переменных процедурного типа.

Подобный механизм мог бы использоваться для обработчиков событий, но есть проблема. Она состоит в том, что это должны быть не процедуры, а методы. Отличие метода от процедуры состоит в том, что помимо явно описанных параметров методу всегда неявно передается указатель на вызвавший его экземпляр класса (переменная Self). Для этого используется тип указатель на метод. При этом в описании процедурного типа использовать слова of object:

type

TMethod = procedure of object;

TNotifyEvent = procedure(Sender: TObject) of object;

В действительности тип указатель на метод представляет собой пару указателей -указатель на сам метод и указатель на объект, к которому он относится. Пример использования типа указатель на метод:

type

TNotifyEvent = procedure(Sender: TObject) of object;

TMainForm = class(TForm)

procedure Buttonclick(Sender: TObject);

. . .

end;

var

MainForm: TMainForm;

onclick: TNotifyEvent

Допустимо следующее присваивание:

onclick := MainForm.Buttonclick;

Типы указатель на процедуру и указатель на метод не совместимы, что приводит к следующей проблеме - обработчики событий обязаны быть чьими-то методами. Иначе им будет соответствовать указатель на процедуру, а не указатель на метод. Возможный путь решения - сгруппировать все обработчики в каком-либо объекте. Например, в Delphi обработчики событий всех визуальных компонентов формы являются методами формы. Возможно создание специального объекта, чье единственное предназначение - быть носителем методов, которые делегируются другим объектам.

Механизм делегирования дает возможность изменять обработчики событий объекта, не меняя описания класса. При этом, существует как вероятность того, что за время выполнения программы для обработки какого либо события объекта будут использоваться разные обработчики, так и то, что один обработчик будет использоваться для разных объектов. Второй случай удобен тем, что приходится писать меньше кода, но приводит к проблеме: иногда обработчик должен различать, какой объект его вызвал. Тут могут быть два варианта - если надо различать класс вызвавшего объекта или если надо различать экземпляры одного класса.

В первом случае используется оператор is, который предназначен для проверки совместимости по присваиванию объекта с классом и возвращает true, если типы совместимы (т.е. объект является экземпляром данного класса или одного из его потомков) и False в ином случае.

Procedure XXX(Sender:Tobject,...)

begin

If (Sender is TObjectType) then ...

. . .

End;

Если же необходимо различать объекты одного класса, то приходится прибегать к дополнительным ухищрениям. Типовой прием - использовать свойство , которое есть у всех компонентов. Например: пусть у формы есть несколько переключателей. Для того чтобы при нажатии каждый из них окрашивался в свой цвет, нужно в Инспекторе Объектов присвоить свойству Tag этих переключателей значения от 0 до 7 и для каждого связать событие onclick с одним и тем же обработчиком события, который анализирует значение свойства Tag вызвавшего его объекта и в зависимости от его значения окрашивает переключатель в нужный цвет. При этом, возможно, придется приводить тип объекта, для которого произошло событие -параметр DendenTObject - к его реальному типу TButton и т.п.