Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Рассылка событий группе объектов. [D7, WinXP]


Страницы:   1 2 3

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
> - это классы, как их в вариант пихать ?


Точно также, как все остальное ? :)


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо