![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Рассылка событий группе объектов. [D7, WinXP]
GuAV © (2005-11-23 14:09:00)Здравсвуйте.
Мне нужно вызывать из одного объекта методы (обработчики событий) нескольких других объектов, причём вызывающий заранее не знает каким объектам, но целевой класс "знает", какие события ему нужны (и предоставляет свои методы).
В данный момент используется следующее решение:
type
TOnCaptionChanged = procedure(Sender: TObject; const Old, New: string) of object;
TOnObjectChanged = procedure(Sender: TMapPolygon) of object;
...
type
TObjChangeNotifier = class(TObject)
private
FCaptionChanged, FObjectChanged, ... : TList;
procedure RegisterHandler(List: TList; const Handler: TMethod; Register: Boolean);
procedure ClearList(List: TList);
public
constructor Create;
destructor Destroy; override;
procedure CaptionChanged(Sender: TObject; const Old, New: string);
procedure ObjectChanged(Sender: TMapPolygon);
...
procedure RegisterOnCaptionChanged(const Handler: TOnCaptionChanged; Register: Boolean);
procedure RegisterOnObjectChanged(const Handler: TOnObjectChanged; Register: Boolean);
...
end;
constructor TObjChangeNotifier.Create;
begin
inherited;
FCaptionChanged := TList.Create;
FObjectChanged := TList.Create;
...
end;
destructor TObjChangeNotifier.Destroy;
begin
ClearList(FCaptionChanged);
ClearList(FObjectChanged);
...
FreeAndNil(FCaptionChanged);
FreeAndNil(FObjectChanged);
...
inherited;
end;
procedure TObjChangeNotifier.RegisterHandler(List: TList;
const Handler: TMethod; Register: Boolean);
var
M: PMethod;
I: Integer;
begin
with List do
if Register then
begin
New(M);
M^ := Handler;
Add(M);
end
else
begin
for I := Count - 1 downto 0 do
begin
M := PMethod(Items[I]);
with M^ do
if (Code = Handler.Code) and (Data = Handler.Data) then
begin
Delete(I);
Dispose(M);
end;
end;
end
end;
procedure TObjChangeNotifier.CaptionChanged(Sender: TObject; const Old,
New: string);
var
I: Integer;
begin
with FCaptionChanged do
for I := 0 to Count - 1 do
TOnCaptionChanged(PMethod(Items[I])^)(Sender, Old, New);
end;
procedure TObjChangeNotifier.ObjectChanged(Sender: TMapPolygon);
var
I: Integer;
begin
with FObjectChanged do
for I := 0 to Count - 1 do
TOnObjectChanged(PMethod(Items[I])^)(Sender);
end;
....
procedure TObjChangeNotifier.RegisterOnCaptionChanged(
const Handler: TOnCaptionChanged; Register: Boolean);
begin
RegisterHandler(FCaptionChanged, TMethod(Handler), Register);
end;
procedure TObjChangeNotifier.RegisterOnObjectChanged(
const Handler: TOnObjectChanged; Register: Boolean);
begin
RegisterHandler(FObjectChanged, TMethod(Handler), Register);
end;
...
в AfterConstruction целевого класса
ObjChangeNotifier.RegisterOnCaptionChanged(CaptionChanged, True);
в BeforeDestruction целевого класса
ObjChangeNotifier.RegisterOnCaptionChanged(CaptionChanged, False);
Вызов события
ObjChangeNotifier.ObjectChanged(CurrentObject);
Однако, очевидно, что для большого числа методов такое решение не является удобным. Есть идея перейти на использование сообщений delphi, тогда TObjChangeNotifier будет содержать только один список (или он сам и будет наседником списка) и один метод диспетчеризации (возможно даже его же TObject.Dispatch), при этом элементами будут не методы а сами объекты (избавление от New и Dsipose). Какие возможны проблемы при этом и как это лучше реализовать ?
Методы должны вызываться синхронно. Порядок роли не играет. Число параметров различных методов различно, переменных параметров нет. Производительность (быстродейтсвие) является фактором, однако удобство главнее.
Хотелось бы услышать ваше мнение как удобнее реализовать рассылку событий группе объектов - через сообщения Delphi, сообщения Windows или другим способом - и как именно. Вообще, как Вы реализуете (или реализовали бы) сабж в своих проектах.
Владислав (2005-11-24 17:56:00) [21]
> Тогда поясните мысль, что значит "зависимость между классами".
> Подписчик в любом случае должне знать что за класс он принимает.
Должен то он должен. Но при добавлении нового класса события в издателя, подписчики, которые не изменялись, начнут выкидывать исключения? А может новое добавленное событие им совсем не нужно.
> Если речь идёт об игнорировании старых классов, nо исключении
> при вновь появившихся, то возможно определить константу
> EmptyEventNotification (пустой классовый метод) и регистрировать
> её так
> Advise(EmptyEventNotification,
> [все, известные, приёмнику, события], True).
>
> Альтернатива Advise была предложена для возможности избежания
> того самого case с is. Можно задать различные методы для
> различных классов событий, при этом в параметре уже правильный
> класс, а приводить при Advise.
Не совсем понял эту мысль.
GuAV (2005-11-24 17:38:00) [22]
> 2). А хорошо ли, если остальные подписчики событие не
> получат?
Исключение может быть поднято после рассылки и содержать информацию обо всех подписчиках, не принявших свои сообщения.
> 1). Я именно это и пытался обойти. Зависимость между
> классами.
Тогда поясните мысль, что значит "зависимость между классами". Подписчик в любом случае должне знать что за класс он принимает.
Если речь идёт об игнорировании старых классов, nо исключении при вновь появившихся, то возможно определить константу EmptyEventNotification (пустой классовый метод) и регистрировать её так
Advise(EmptyEventNotification,
[все, известные, приёмнику, события], True).
Альтернатива Advise была предложена для возможности избежания того самогоcaseсis. Можно задать различные методы для различных классов событий, при этом в параметре уже правильный класс, а приводить при Advise.
Владислав (2005-11-24 17:22:00) [23]
function Advise(AEventNotification: TEventNotification;
AcceptedEvents: array of TClass; // 1).
RaiseExceptionIfUnaccepted: Boolean = False // 2).
): Integer; overload;
1). Я именно это и пытался обойти. Зависимость между классами.
2). А хорошо ли, если остальные подписчики событие не получат?
GuAV (2005-11-24 17:06:00) [24]Владислав © (24.11.05 9:03) [13]
Теперь вроде всё ясно.
Моя пропозиция:
function Advise(AEventNotification: TEventNotification): Integer; overload;
function Advise(AEventNotification: TEventNotification;
AcceptedEvents: array of TClass;
RaiseExceptionIfUnaccepted: Boolean = False): Integer; overload;
procedure Unadvise(AdviseID: Integer); overload;
procedure Unadvise(AEventNotification: TEventNotification); overload;
GuAV (2005-11-24 16:50:00) [25]Про реакцию на Action я понял следующее: у компонента есть реакция на Action в ExecuteAction; по умолчанию, если у компонента нет особой реакции на Action, сам Action реагирует на компонент (как в паттерн Visitor).
Как правильно запустить диспетчеризацию Action, что делает ActionLink ?
> Если нет возможности (желания) сделать все на основе
> базового класса
возможности (желания) нет.
> то в нашем распоряжении есть Tag в котором можно
>сохранить ссылку на
> тот самый метод, который будет вызываться по списку.
Не хочу "один метод", лучше много методов.
> Если внимательно и мучительно размышлять, то окажется,
>что
> интерфейса TNotifyEvent вполне хватит для своих
>событий, т.е.,
> для тех, что происходят в собственном приложении.
Вот например событиеFOnCaptionChanged: procedure(Sender: TObject; const Old, New: string) of object;. Где получатель возьмёт старый Caption ?
> Для взаимодействия с другими приложениями
Чур меня :) Здесь одно приложение, своих dll нет и даже один поток.
> Ну, а по поводу Notification - это должно быть
>правилом при любом
> использовании ссылок на другие компоненты .
Ладно. Посмотрю, сделать ли TObjEvents компонентом (с Owner заместо Creator), или делать нотификацию вручную.
Владислав (2005-11-24 16:33:00) [26]
> Владислав © (24.11.05 9:03) [10]
>
> Спасибо. Интересно. Но сложновато.
На самом деле нет. Даже реализация идеи проста (пост 11), а использование еще проще.
> Как я понял идею (поправьте если не так):
> Существуют один или несколько классов списков событий, все
> они агрегируют TEventConnectionPoint для реализации IEventConnectionPoint.
>
>
> TEventConnectionPoint является фактическим менеджером списка
> подписчиков и диспетчером событий, он готов разослать всем
> подписчикам любого наследника TEventObject.
>
> У подписчиков единый обработчик типа TEventNotification.
> События различаются по Id, затем явно приводятся к нужному
> классу-наследнику TEventObject.
Ну где-то верно поняли, а где-то кто-то другого не понял :)
Я своими словами изложу суть. Может это что-то прояснит.
1. Есть какой-то объект (пусть это будет тип № 1) (экземпляр какого-то класса), в котором могут происходить различные по своей сути события. Этот объект может оповещать об этих событиях желающих (В моей ситуации такой класс уже был написан до меня, но оповещения не было, а желающие появились :-) ).
2. Есть какие-то объекты (пусть их тип будет № 2) (экземпляры других и различных классов, не связанных общим предком, ну кроме TObject :-) ), которым было бы неплохо получать уведомления о каких-то событиях, происходящих в каких-то объектах. Замечу, что не всегда, опять же, у последних может быть общий предок, кроме TObject.
Это я свою конкретную ситуацию изложил.
Ну а теперь в этой ситуации нужно организовать посылку уведомлений.
Вот последовательность действий.
1. Объект первого типа "объявляет события", о которых он будет уведомлять. В данном случае нужно объявить целочисленные константы.
2. Объект первого типа "агрегирует" экземпляр TEventConnectionPoint.
Т.е. в класс добавляем наследование интерфейса IEventConnectionPoint. Где-то, например в конструкторе/деструкторе добавляем создание/уничтожение экземпляра TEventConnectionPoint.
3. Объект первого типа "добавляет" уведомления о событиях. Т.е. в местах кода, где возникает событие, добавляем код вызова TEventConnectionPoint.Notify.
4. В объекты второго типа добавляется метод, который будет вызываться при событии.
5. В объекты второго типа добавляется код Advise/Unadvise.
>
> Преимущества:
> - Один цикл рассылки и одна реализация регистратора не только
> для всех событий списка, но и для всех списков событий.
> Очень хорошо.
+ Ни подписчики ни отправитель уведомлений не обязаны быть наследниками каких то классов, значит можно добавить эту реализацию в уже существующую систему.
+ По той же причине, что и в предыдущем случае... Ни подписчик, ни отправитель могут ничего не знать ни о интерфейсе, ни о реализации друг-друга. Возможно независимое изменение.
+ К одному отправителю уведомлений могут подписаться несколько подписчиков.
+ Один подписчик может подписаться на уведомления разных подписчиков. Для этого не обязательно писать дополнительный код.
+ Подписчик не обязан реагировать на все уведомления. При этом исключается написание дополнительного кода.
+ Возможность отправки/получения уведомлений, содержащих фактически любой объем информации о событии.
> Недостатки:
> - Сложная реализация.
> - Один обработчик с большим case. Это неприятно. (именно
> по этому я пытался завязаться на сообщениях).
> - Необходимость запоминать свой AdviseID. Я бы возможно
> сделал поиск TEventNotification в Unadvise для исключения
> этого (на этом ИМХО много не потеряешь).
На счет сложности я уже сказал.
На счет case - это единственное место, где нужно писать возможно (!) объемный код. Однако заметьте, на сообщениях вы так или иначе напишите что-то, что будет аналогом.
Запомнить свой AdviseID - 4 байта памяти и пара дополнительных строк кода. Уберем запоминание - вычеркнем некоторые плюсы. Так что спорно.
- На мой взгляд самый большой минус, это обработка каких то сложных событий, для которых пишется свой класс. Приходится проверять и приводить тип события. Здесь, чтобы избежать долгих часов поиска, почему же не приходит уведомление после некоторых изменений классов, нужно наверное генерировать исключение, если получили не тот тип, который ожидали. У меня этого нет. Подумаю над этим.
> Посмотрите, кстати, [2] последняя строка, обратный цикл
> и в Вашей реализации будет лучше.
Замечание здравое. Спасибо. Я об этом не задумывался.
Leonid Troyanovsky (2005-11-24 16:23:00) [27]
> GuAV © (24.11.05 16:10) [18]
> Один интерфейс на все события не годится. Т.к. добавление
> новых событий будет связано с
Если внимательно и мучительно размышлять, то окажется, что
интерфейса TNotifyEvent вполне хватит для своих событий, т.е.,
для тех, что происходят в собственном приложении.
Просто, это самый распространенный обработчик (а откуда вдруг
возьмутся другие?).
Для взаимодействия с другими приложениями есть другие решения,
типа издатель-подписчик (см пример в demos).
Ну, а по поводу Notification - это должно быть правилом при любом
использовании ссылок на другие компоненты .
--
Regards, LVT.
Leonid Troyanovsky (2005-11-24 16:14:00) [28]
> Leonid Troyanovsky © (24.11.05 15:47) [17]
>
> > GuAV © (24.11.05 15:06) [14]
Пару слов дополню про TComponent.
Если нет возможности (желания) сделать все на основе базового класса,
то в нашем распоряжении есть Tag в котором можно сохранить ссылку на
тот самый метод, который будет вызываться по списку.
В качестве Sender, видимо, будет использован вызывающий,
а Self - сам элемент списка {TNotifyEvent(TMethod)}.
Можно обойтись и без собственного списка, если сделать вызывающий
компонент Owner вызываемых.
Notification в таком случае он будет получать автоматом.
В любом случае, чем проще и понятнее будет эта модель,
тем волосы будут более пушистыми.
--
Regards, LVT.
GuAV (2005-11-24 16:10:00) [29]Leonid Troyanovsky © (24.11.05 8:40) [9]
>либо
> все классы д.б. собс-ручные, с тем самым интерфейсом,
>который будет
>вызываться по списку.
> Но, и в последнем случае, TComponent предпочтительней,
>бо уже обладает
> механизмом Notification, позволяющим оный список
> обновлять.
Один интерфейс на все события не годится. Т.к. добавление новых событий будет связано с изменением этого интерфейса. Можно завести для каждого события (или для небольших групп событий) свой интерфейс. Тогда при вызове события диспечтер запрашивает у каждого подписчика интерфейс события и вызывает его при его наличии.
Однако, это ничем не лучше варианта [15], т.к. всё равно несколько циклов.
Вообще говоря, чтобы обеспечить единый цикл рассылки требуется ввести единый тип параметра события - в любом случае (ассемблер и манипуляция с моделями вызовов неприемлимы, при том и они вряд ли бы помогли). Это уже можно сделать при варианте [15], передавая в каждое событие один var параметр (а для цикла расылки он будет нетипизированным var параметром). Я посмотрю, стоит ли.
Notification не нужен, желательна подписка и отписка вручную.
Leonid Troyanovsky (2005-11-24 15:47:00) [30]
> GuAV © (24.11.05 15:06) [14]
> Пожалуйста, если можно, подробнее. Дело в том, что, как
> сейчас уже выяснилось все "подписчики" являются компонентами,
> ActionList-ы используются. Но я так и не понял, как создать
> свой Aсtion со своими параметрами и запускать его.
Имелось ввиду, что нужна реализация Actions for TComponent,
бо Actions for TWincontrol расчитана на видимые контролы.
Ну, а реализацию можно подсмотреть в том же VCL.
А если уж выяснилось, что это компоненты, то, по-крайней мере,
необходимо использовать FreeNotification для обновления списка.
--
Regards, LVT.
GuAV (2005-11-24 15:40:00) [31]GuAV © (24.11.05 15:08) [15]
>EventsList.Remove(Self);
EventsList.Extract(Self);
т.к. я перешел на владеющий TObjectList.
GuAV (2005-11-24 15:08:00) [32]Сейчас используется вариант jack128 - лучше чем было.
type
TObjEvents = class(TObject)
private
FCreator: TObject;
public
...
FOnObjectsChanged: procedure(Sender: TList) of object;
FOnMatrixChanged: procedure(Sender: TObject) of object;
constructor Create(Creator: TObject);
destructor Destroy; override;
...
class procedure DoObjectChanged(Sender: TMapPolygon);
class procedure DoMatrixChanged(Sender: TObject);
end;
...
class function TObjEvents.Find(Creator: TObject): TObjEvents;
var I: Integer;
begin
for I := EventsList.Count - 1 downto 0 do
with TObjEvents(EventsList[I]) do
if FCreator = Creator then
begin
Result := TObjEvents(EventsList[I]);
Exit;
end;
Result := nil;
end;
...
class procedure TObjEvents.DoObjectChanged(Sender: TMapPolygon);
var I: Integer;
begin
for I := EventsList.Count - 1 downto 0 do
with TObjEvents(EventsList[I]) do
if Assigned(FOnObjectChanged) then
FOnObjectChanged(Sender);
end;
...
constructor TObjEvents.Create(Creator: TObject);
begin
inherited Create;
FCreator := Creator;
EventsList.Add(Self);
end;
destructor TObjEvents.Destroy;
begin
EventsList.Remove(Self);
inherited;
end;
initialization
EventsList := TObjectList.Create;
...
finalization
...
EventsList.Free;
end.
Подписка:
with TObjEvents.Create(Self) do
begin
FOnObjectChanged := ObjectChanged;
FOnObjectsChanged := ObjectsChanged;
FOnGeoCoordChanged := GeoCoordChanged;
end;
Отписка:
TObjEvents.Find(Self).Free;
Запуск:
TObjEvents.DoMatrixChanged(Self);
Преимущества:
- Очень простые запуск, подписка и отписка.
- "Натуральные" события, свой обработчик для каждого события.
- Простая и "легковесная" реализация.
- Единый список.
Недостаток:
- Свой цикл для каждого события.
GuAV (2005-11-24 15:06:00) [33]Leonid Troyanovsky © (24.11.05 8:40) [9]
> С сообщениями все в порядке, дело в твоей реализации.
Наверное. В любом случае сообщения не самоцель, цель сабж, поэтому больше тратить времени на них не хочу.
> А, вообще-то, сама исходная постановка хромает на обе
> ноги.
?
Думаю нет.
> Т.е., либо нужна модель, основанная на TComponent with
>Actions, либо
> все классы д.б. собс-ручные, с тем самым интерфейсом,
>который будет
>вызываться по списку.
> Но, и в последнем случае, TComponent предпочтительней,
>бо уже обладает
> механизмом Notification, позволяющим оный список
> обновлять.
Пожалуйста, если можно, подробнее. Дело в том, что, как сейчас уже выяснилось все "подписчики" являются компонентами, ActionList-ы используются. Но я так и не понял, как создать свой Aсtion со своими параметрами и запускать его.
Владислав © (24.11.05 9:03) [10]
Спасибо. Интересно. Но сложновато.
Как я понял идею (поправьте если не так):
Существуют один или несколько классов списков событий, все они агрегируют TEventConnectionPoint для реализации IEventConnectionPoint.
TEventConnectionPoint является фактическим менеджером списка подписчиков и диспетчером событий, он готов разослать всем подписчикам любого наследника TEventObject.
У подписчиков единый обработчик типа TEventNotification. События различаются по Id, затем явно приводятся к нужному классу-наследнику TEventObject.
Преимущества:
- Один цикл рассылки и одна реализация регистратора не только для всех событий списка, но и для всех списков событий. Очень хорошо.
Недостатки:
- Сложная реализация.
- Один обработчик с большим case. Это неприятно. (именно по этому я пытался завязаться на сообщениях).
- Необходимость запоминать свой AdviseID. Я бы возможно сделал поиск TEventNotification в Unadvise для исключения этого (на этом ИМХО много не потеряешь).
Посмотрите, кстати, [2] последняя строка, обратный цикл и в Вашей реализации будет лучше.
Владислав (2005-11-24 09:21:00) [34]
// Подписка на события клиентом.
constructor TfmAlerts.Create(AOwner: TComponent);
var
i: Integer;
EventList: TIBEventList;
EventInfo: TIBEventInfo;
ConnectionPoint: IEventConnectionPoint;
begin
inherited;
...
EventList := TIBEventList.Create;
try
// Получили интерфейс
ConnectionPoint := EventList;
// подписались и сохранили результат (идентификатор подписки)
FAdviseID := ConnectionPoint.Advise(EventNotification);
...
finally
// Не удивляйтесь, что мы уничтожаем экземпляр.
// Он у нас "одиночка" с подсчетом ссылок
EventList.Free;
end
end;
// Отписка
destructor TfmAlerts.Destroy;
var
EventList: TIBEventList;
ConnectionPoint: IEventConnectionPoint;
begin
EventList := TIBEventList.Create;
try
ConnectionPoint := EventList;
ConnectionPoint.Unadvise(FAdviseID);
finally
EventList.Free;
end;
...
inherited;
end;
// Обработка событий
procedure TfmAlerts.EventNotification(const AEventObject: TEventObject);
var
AddEventObject: TIBEventListNotifyObject;
EventInfo: TIBEventInfo;
begin
try
case AEventObject.EventID of
Integer(EVENTID_IB_EVENT_LIST_CLEAR):
begin
tlAlerts.Clear;
end;
Integer(EVENTID_IB_EVENT_LIST_ADD):
begin
if AEventObject is TIBEventListNotifyObject then
begin
AddEventObject := TIBEventListNotifyObject(AEventObject);
EventInfo := AddEventObject.EventInfo;
AddEvent(EventInfo.DatabaseName, EventInfo.EventName,
EventInfo.EventCount, EventInfo.DateTime);
end
end;
end;
except
Application.HandleException(Self);
end
end;
Владислав (2005-11-24 09:16:00) [35]Продолжение...
Какой-то класс должен уведомлять о своих изменениях.
Вот простой пример реализации:
// идентификаторы событий:
TIB_EVENT_LIST_IDS = (
EVENTID_IB_EVENT_LIST_ADD = 0,
EVENTID_IB_EVENT_LIST_CLEAR = 1
);
// Это класс для объектов специального события. В нем есть дополнительная информация о событии
TIBEventListNotifyObject = class(TEventObject)
private
FEventInfo: TIBEventInfo;
public
property EventInfo: TIBEventInfo read FEventInfo;
end;
// Класс, реализующий подписку на свои события
// (только ключевые моменты)
// Интерфейс подписчика
TIBEventList = class(TObject, IEventConnectionPoint)
...
// Агрегируем объект, который реализует подписку
FEventConnectionPoint: TEventConnectionPoint;
...
property EventConnectionPoint: TEventConnectionPoint
read FEventConnectionPoint implements IEventConnectionPoint;
// Оповещение о простых событиях
procedure TIBEventList.Clear;
var
LocalEventObject: TEventObject;
...
begin
...
with EventConnectionPoint do
begin
LocalEventObject :=
GetSimpleEventObject(Self, Integer(EVENTID_IB_EVENT_LIST_CLEAR));
Notify(LocalEventObject);
end;
end;
// А это событие посложнее...
procedure TIBEventList.AddEvent(const Database: string; Event: string;
Count: Integer);
var
LocalEventObject: TIBEventListNotifyObject;
...
begin
...
LocalEventObject :=
TIBEventListNotifyObject.Create(Self, Integer(EVENTID_IB_EVENT_LIST_ADD));
try
LocalEventObject.FEventInfo := EventInfo;
EventConnectionPoint.Notify(LocalEventObject);
finally
LocalEventObject.Free;
end;
end;
А сейчас покажу подписку и получение событий клиентом...
Владислав (2005-11-24 09:04:00) [36]Уважаемые модераторы, удалите предыдущий пост пожалуйста. Забыл про форматирование :(
А если кинуть взгляд на IConnectionPoint? На мой взгляд верх гибкости. :)
Когда подобный вопрос возник, это было первое, что пришло на ум. Однако, нужно было, чтобы реализация плавно вписалась в инфраструктуру приложений, которые пишу не я один. Поэтому после некоторых упрощений пришел к такой реализации. Может что-то для себя подчерпнете.
// Классы для реализации подписки на события
// Класс события
// Класс, который реализует подписку на свои события создает
// экземпляр класса TEventObject перед уведомлением подписчиков о событии
TEventObject = class(TObject)
private
FEventID: Integer;
FSender: TObject;
protected
// Наследники могу воспользоваться этим методом для установки read only
// свойств Sender и EventID
procedure SetEventInfo(ASender: TObject; AEventID: Integer);
public
constructor Create(ASender: TObject; AEventID: Integer); virtual;
// Экземпляр отправитель события
property Sender: TObject read FSender;
// Идентификатор события
// Каждый класс реализующий подписку на свои события создает карту
// идентификаторов
property EventID: Integer read FEventID;
end;
// Тип метода-события уведомления
TEventNotification = procedure(const AEventObject: TEventObject) of object;
// Экземпляры этого класса предназначены для хранения информации
// о зарегистрированных приемниках уведомлений о событии
// Класс предназначен только для хранения информации
// Не пытайтесь создавать или освобождать экземпляры этого класса
TEventConnectionInfo = class(TObject)
private
FOnEventNotification: TEventNotification;
public
constructor Create(AEventNotification: TEventNotification);
procedure Notify(EventObject: TEventObject);
end;
// Базовый интерфейс для точек соединения с классами, реализующими подписку
// на события
// Интерфейс предназначен для реализации подписки в классах, которые не могут
// наследоваться от TEventConnectionPoint (или это не целесообразно)
// Для реализации этого интерфейса в передатчике событий можно использовать
// композицию класса TEventConnectionPoint
IEventConnectionPoint = interface(IUnknown)
['{C0BAC6F7-B6A3-44CA-B8AC-F9ECE673E57C}']
// Подписаться на события
// Вызывается объектом, желающим подписаться на события
// Результат функции - идентификатор подписки, используется
// при вызове метода Unadvise для отписки
function Advise(AEventNotification: TEventNotification): Integer;
// Отписаться от события, AdviseID - идентификатор подписки
procedure Unadvise(AdviseID: Integer);
end;
// Класс, реализующий функциональность подписки на события и отписки от них
// К одному и тому же отправителю событий может подписаться несколько клиентов
// Подписка клиентов хранится в списке подписчиков
TEventConnectionPoint = class(TUnknownBase)
private
FAdviseList: TList;
FEventObject: TEventObject;
function GetAdviseList: TList;
function GetCount: Integer;
function GetEventObject: TEventObject;
private
property AdviseList: TList read GetAdviseList;
property Count: Integer read GetCount;
property EventObject: TEventObject read GetEventObject;
public
destructor Destroy; override;
// Подписаться на событие (см. IEventConnectionPoint.Advise)
function Advise(AEventNotification: TEventNotification): Integer;
// Отписаться от события (см. IEventConnectionPoint.Unadvise)
procedure Unadvise(AdviseID: Integer);
// Получить экземпляр класса TEventObject и установить его свойства
// Эта функция-помощник предназначена для отправки уведомлений о событиях
// класса TEventObject. Функция создает класс только один раз и кеширует
// экземпляр TEventObject
// Не освобождайте экземпляр, полученный от этой функции.
// если необходимы более сложные объекты события - создавайте их самостоятельно
function GetSimpleEventObject(ASender: TObject; AEventID: Integer): TEventObject;
// Послать уведомления о событии AEventObject всем подписчикам
procedure Notify(AEventObject: TEventObject);
end;
// ******************************************************
{ TEventObject }
constructor TEventObject.Create(ASender: TObject; AEventID: Integer);
begin
SetEventInfo(ASender, AEventID);
end;
procedure TEventObject.SetEventInfo(ASender: TObject; AEventID: Integer);
begin
FSender := ASender;
FEventID := AEventID;
end;
{ TEventConnectionInfo }
constructor TEventConnectionInfo.Create(
AEventNotification: TEventNotification);
begin
FOnEventNotification := AEventNotification;
end;
procedure TEventConnectionInfo.Notify(EventObject: TEventObject);
begin
FOnEventNotification(EventObject);
end;
{ TEventConnectionPoint }
destructor TEventConnectionPoint.Destroy;
begin
FreeAndNil(FEventObject);
FreeAndNil(FAdviseList);
inherited;
end;
function TEventConnectionPoint.GetEventObject: TEventObject;
begin
if FEventObject = nil then
FEventObject := TEventObject.Create(nil, 0);
Result := FEventObject;
end;
function TEventConnectionPoint.GetSimpleEventObject(ASender: TObject;
AEventID: Integer): TEventObject;
begin
Result := EventObject;
Result.SetEventInfo(ASender, AEventID);
end;
function TEventConnectionPoint.GetAdviseList: TList;
begin
if FAdviseList = nil then
FAdviseList := TList.Create;
Result := FAdviseList
end;
function TEventConnectionPoint.GetCount: Integer;
begin
if FAdviseList <> nil then
Result := FAdviseList.Count
else
Result := 0;
end;
function TEventConnectionPoint.Advise(
AEventNotification: TEventNotification): Integer;
var
ConnectionInfo: TEventConnectionInfo;
LocalAdviseList: TList;
begin
LocalAdviseList := AdviseList;
LocalAdviseList.Expand;
ConnectionInfo := TEventConnectionInfo.Create(AEventNotification);
LocalAdviseList.Add(ConnectionInfo);
Result := Integer(Pointer(ConnectionInfo));
end;
procedure TEventConnectionPoint.Unadvise(AdviseID: Integer);
var
Index: Integer;
ConnectionInfo: TEventConnectionInfo;
LocalAdviseList: TList;
begin
ConnectionInfo := TEventConnectionInfo(Pointer(AdviseID));
Assert(Count > 0);
if Count = 0 then
Exit;
LocalAdviseList := AdviseList;
Index := LocalAdviseList.IndexOf(ConnectionInfo);
Assert(Index >= 0);
if Index < 0 then
Exit;
LocalAdviseList.Delete(Index);
ConnectionInfo.Free;
end;
procedure TEventConnectionPoint.Notify(AEventObject: TEventObject);
var
i: Integer;
LocalAdviseList: TList;
ConnectionInfo: TEventConnectionInfo;
begin
if Count > 0 then
begin
LocalAdviseList := AdviseList;
for i := 0 to LocalAdviseList.Count - 1 do
begin
ConnectionInfo := TEventConnectionInfo(LocalAdviseList.Items[i]);
ConnectionInfo.Notify(AEventObject);
end;
end
end;
//***************************************************
Сейчас еще дам пример использования...
Владислав (2005-11-24 09:03:00) [37]А если кинуть взгляд на IConnectionPoint? На мой взгляд верх гибкости. :)
Когда подобный вопрос возник, это было первое, что пришло на ум. Однако, нужно было, чтобы реализация плавно вписалась в инфраструктуру приложений, которые пишу не я один. Поэтому после некоторых упрощений пришел к такой реализации. Может что-то для себя подчерпнете.
// Классы для реализации подписки на события
// Класс события
// Класс, который реализует подписку на свои события создает
// экземпляр класса TEventObject перед уведомлением подписчиков о событии
TEventObject = class(TObject)
private
FEventID: Integer;
FSender: TObject;
protected
// Наследники могу воспользоваться этим методом для установки read only
// свойств Sender и EventID
procedure SetEventInfo(ASender: TObject; AEventID: Integer);
public
constructor Create(ASender: TObject; AEventID: Integer); virtual;
// Экземпляр отправитель события
property Sender: TObject read FSender;
// Идентификатор события
// Каждый класс реализующий подписку на свои события создает карту
// идентификаторов
property EventID: Integer read FEventID;
end;
// Тип метода-события уведомления
TEventNotification = procedure(const AEventObject: TEventObject) of object;
// Экземпляры этого класса предназначены для хранения информации
// о зарегистрированных приемниках уведомлений о событии
// Класс предназначен только для хранения информации
// Не пытайтесь создавать или освобождать экземпляры этого класса
TEventConnectionInfo = class(TObject)
private
FOnEventNotification: TEventNotification;
public
constructor Create(AEventNotification: TEventNotification);
procedure Notify(EventObject: TEventObject);
end;
// Базовый интерфейс для точек соединения с классами, реализующими подписку
// на события
// Интерфейс предназначен для реализации подписки в классах, которые не могут
// наследоваться от TEventConnectionPoint (или это не целесообразно)
// Для реализации этого интерфейса в передатчике событий можно использовать
// композицию класса TEventConnectionPoint
IEventConnectionPoint = interface(IUnknown)
['{C0BAC6F7-B6A3-44CA-B8AC-F9ECE673E57C}']
// Подписаться на события
// Вызывается объектом, желающим подписаться на события
// Результат функции - идентификатор подписки, используется
// при вызове метода Unadvise для отписки
function Advise(AEventNotification: TEventNotification): Integer;
// Отписаться от события, AdviseID - идентификатор подписки
procedure Unadvise(AdviseID: Integer);
end;
// Класс, реализующий функциональность подписки на события и отписки от них
// К одному и тому же отправителю событий может подписаться несколько клиентов
// Подписка клиентов хранится в списке подписчиков
TEventConnectionPoint = class(TUnknownBase)
private
FAdviseList: TList;
FEventObject: TEventObject;
function GetAdviseList: TList;
function GetCount: Integer;
function GetEventObject: TEventObject;
private
property AdviseList: TList read GetAdviseList;
property Count: Integer read GetCount;
property EventObject: TEventObject read GetEventObject;
public
destructor Destroy; override;
// Подписаться на событие (см. IEventConnectionPoint.Advise)
function Advise(AEventNotification: TEventNotification): Integer;
// Отписаться от события (см. IEventConnectionPoint.Unadvise)
procedure Unadvise(AdviseID: Integer);
// Получить экземпляр класса TEventObject и установить его свойства
// Эта функция-помощник предназначена для отправки уведомлений о событиях
// класса TEventObject. Функция создает класс только один раз и кеширует
// экземпляр TEventObject
// Не освобождайте экземпляр, полученный от этой функции.
// если необходимы более сложные объекты события - создавайте их самостоятельно
function GetSimpleEventObject(ASender: TObject; AEventID: Integer): TEventObject;
// Послать уведомления о событии AEventObject всем подписчикам
procedure Notify(AEventObject: TEventObject);
end;
// ******************************************************
{ TEventObject }
constructor TEventObject.Create(ASender: TObject; AEventID: Integer);
begin
SetEventInfo(ASender, AEventID);
end;
procedure TEventObject.SetEventInfo(ASender: TObject; AEventID: Integer);
begin
FSender := ASender;
FEventID := AEventID;
end;
{ TEventConnectionInfo }
constructor TEventConnectionInfo.Create(
AEventNotification: TEventNotification);
begin
FOnEventNotification := AEventNotification;
end;
procedure TEventConnectionInfo.Notify(EventObject: TEventObject);
begin
FOnEventNotification(EventObject);
end;
{ TEventConnectionPoint }
destructor TEventConnectionPoint.Destroy;
begin
FreeAndNil(FEventObject);
FreeAndNil(FAdviseList);
inherited;
end;
function TEventConnectionPoint.GetEventObject: TEventObject;
begin
if FEventObject = nil then
FEventObject := TEventObject.Create(nil, 0);
Result := FEventObject;
end;
function TEventConnectionPoint.GetSimpleEventObject(ASender: TObject;
AEventID: Integer): TEventObject;
begin
Result := EventObject;
Result.SetEventInfo(ASender, AEventID);
end;
function TEventConnectionPoint.GetAdviseList: TList;
begin
if FAdviseList = nil then
FAdviseList := TList.Create;
Result := FAdviseList
end;
function TEventConnectionPoint.GetCount: Integer;
begin
if FAdviseList <> nil then
Result := FAdviseList.Count
else
Result := 0;
end;
function TEventConnectionPoint.Advise(
AEventNotification: TEventNotification): Integer;
var
ConnectionInfo: TEventConnectionInfo;
LocalAdviseList: TList;
begin
LocalAdviseList := AdviseList;
LocalAdviseList.Expand;
ConnectionInfo := TEventConnectionInfo.Create(AEventNotification);
LocalAdviseList.Add(ConnectionInfo);
Result := Integer(Pointer(ConnectionInfo));
end;
procedure TEventConnectionPoint.Unadvise(AdviseID: Integer);
var
Index: Integer;
ConnectionInfo: TEventConnectionInfo;
LocalAdviseList: TList;
begin
ConnectionInfo := TEventConnectionInfo(Pointer(AdviseID));
Assert(Count > 0);
if Count = 0 then
Exit;
LocalAdviseList := AdviseList;
Index := LocalAdviseList.IndexOf(ConnectionInfo);
Assert(Index >= 0);
if Index < 0 then
Exit;
LocalAdviseList.Delete(Index);
ConnectionInfo.Free;
end;
procedure TEventConnectionPoint.Notify(AEventObject: TEventObject);
var
i: Integer;
LocalAdviseList: TList;
ConnectionInfo: TEventConnectionInfo;
begin
if Count > 0 then
begin
LocalAdviseList := AdviseList;
for i := 0 to LocalAdviseList.Count - 1 do
begin
ConnectionInfo := TEventConnectionInfo(LocalAdviseList.Items[i]);
ConnectionInfo.Notify(AEventObject);
end;
end
end;
//***************************************************
Leonid Troyanovsky (2005-11-24 08:40:00) [38]
> GuAV © (23.11.05 21:11) [8]
> Пока оставил как есть. С delphi сообщениями больше не связываюсь.
С сообщениями все в порядке, дело в твоей реализации.
А, вообще-то, сама исходная постановка хромает на обе ноги.
Т.е., либо нужна модель, основанная на TComponent with Actions, либо
все классы д.б. собс-ручные, с тем самым интерфейсом, который будет
вызываться по списку.
Но, и в последнем случае, TComponent предпочтительней, бо уже обладает
механизмом Notification, позволяющим оный список обновлять.
--
Regards, LVT.
GuAV (2005-11-23 21:11:00) [39]С сообщениями ничего не вышло. При вызове диспача из себя же начинаются различные странности, вроде из цикла for считает выше границы (от этого спасла обёртка PUSHA/POPA вокруг вызова Dispatch ! хотя другого asm-кода нет, абсолютно везде модель register !), и другие странности, например колонки в гридах съезжают. Не исключаю кривизну своих рук, но похоже на глюк RTL.
Пока оставил как есть. С delphi сообщениями больше не связываюсь. К рассмотернию вариант jack128.
Игорь Шевченко (2005-11-23 17:38:00) [40]
> И потом, Sender: TObject, Sender: TMapPolygon, Sender: TList
> - это классы, как их в вариант пихать ?
Точно также, как все остальное ? :)