Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Основная";
Текущий архив: 2005.12.25;
Скачать: [xml.tar.bz2];

Вниз

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

 
GuAV ©   (2005-11-23 14:09) [0]

Здравсвуйте.

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

В данный момент используется следующее решение:

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-23 15:25) [1]

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


 
jack128 ©   (2005-11-23 15:41) [2]

Я делаю так:
 TSomeObjEvents = class
 public
   property Obj: TObjChangeNotifier read write;
   property OnCaptionChanged: TOnCaptionChanged read write;
   property OnObjectChanged: TOnObjectChanged read write;
 end;

Подписчики создают объект TSomeObjEvents и задают свойство Obj. А Obj храниться список соответствующих TSomeObjEvents

GuAV ©   (23.11.05 14:09)
тогда TObjChangeNotifier будет содержать только один список

А какая разница один или несколько?

GuAV ©   (23.11.05 14:09)
и один метод диспетчеризации

Ничего хорошего в этом не вижу

GuAV ©   (23.11.05 14:09)
(избавление от New и Dsipose).

А в чем проблема?

ЗЫ
GuAV ©   (23.11.05 14:09)
  for I := 0 to Count - 1 do
    TOnObjectChanged(PMethod(Items[I])^)(Sender);


Я обычно здесь делаю обратный цикл, чтобы дать возможность подписчику отписаться в самом обработчике события.


 
GuAV ©   (2005-11-23 16:46) [3]

Игорь Шевченко ©   (23.11.05 15:25) [1]

> указывать еще и тип события, на который обработчик
> должен реагировать

Как указывать ? В настоящем варианте просто используются разные методы для регистрации различных событий.

Если завести Enumeration для типа событий, то можно указывать его в методе-"регистраторе". Вы это имеете ввиду?

Если же делать сообщения или нечто "сообщенияподобное", то необходимость указания типа обработчика отпадает - класс безусловно готов принять все события, а нужны ли они ему - он сам решит.

При использовании именно message-методов обработчика может не существовать - однако меня интересует, какая реакция будет при этом. Существуют контролы (фреймы) с созданным или несозданным окном, а также прямые наследники TObject, и хотелось бы обеспечить отсутствие реакции на необрабатываемое во всех трёх случаях. Возможно ли
это, и если да то как ?

Игорь Шевченко ©   (23.11.05 15:25) [1]

> а параметры передавать одним Variant (в который можно
> поместить массив других вариантов).


Рассмотрю как вариант. Однако, как мне кажется удобнее принимать один нетипизированый const или var параметр (в обработчике привести к нужному Record), а если таки ипользовать сообщения Delphi, то это - "естественный" путь.

Что касается заполнения - в варианте с const или var параметром в классе-генераторе события объявляется нужная запись и заполняется - это более удобно, так как есть типизация, в отличие от Variant.

jack128 ©   (23.11.05 15:41) [2]

> Я делаю так:


Я понял. Идея ApplicationEvents. смотрел. Особенно хорошо для визуального применения, если TSomeObjEvents - компонент и зарегистрирован (этот вариант тоже рассматривал).


>>тогда TObjChangeNotifier будет содержать только один список
> А какая разница один или несколько?

Обрати внимание на код [0]. Каждый метод-диспетчер сканит свой список, в комструкторе и деструкторе все списки упоминаются по разу - неудобно, главная причина отказа.


>>и один метод диспетчеризации
> Ничего хорошего в этом не вижу

А какая разница один или несколько? © jack128
Буду ли я указывать индексом события или именем метода - ИМХО безразлично.

Зато один метод - и цикл писать один.

Теряется типизация, но её можно вернуть пачкой методов, в них запускать главный с соотв. индексом.

Кстати, при использовании индексных свойств заместо методов, можно из одного сделать несколько. Но это уже типизацию не восстановит.


>>(избавление от New и Dsipose).
> А в чем проблема?

Да, не так чтобы сильно, но лишнее выделение/освобождение и лишний уровень косвенности. Мелочь, но неприятно.


> Я обычно здесь делаю обратный цикл, чтобы дать
> возможность подписчику отписаться в самом обработчике
> события.

Спасибо. Но мне пока не требуется - подписка или до BeforeDestruction или вообще пожизненная (до Destroy).

Ещё была идея: интерфейс, реализованный диспетчером и классами. Преимущество - максимальная типизация.
Но сущесвенный недостаток: необходимость добавления пустого метода в каждый класс при появлении нового события.

Склоняюсь к варианту:

Сообщения, для каждого события своё.
События:

 TOnCaptionChanged = record
   Message: LongWord; { = WM_USER + 3}
   Sender: TObject;
   Old, New: string;
 end;

 TOnObjectChanged = record
   Message: LongWord; { = WM_USER + 4}
   Sender: TMapPolygon;
 end;
 
 ...

Главный метод диспетчеризации - TObject.Dispatch диспечера, вспомогательные по именам событий для типизации входного recordа, заполняют Message и вызывают главный. Жаль, что строки и варианты не допускаются в переменной части записи.


 
Игорь Шевченко ©   (2005-11-23 16:57) [4]

GuAV ©   (23.11.05 16:46) [3]


> Если завести Enumeration для типа событий, то можно указывать
> его в методе-"регистраторе". Вы это имеете ввиду?


Например. Или просто константу. Я не совсем понимаю, какой смысл использовать сообщения (Windows или Delphi, не слишком большая разница).

Я бы использовал именно Enumeration, чтобы возможные ошибки (опечатки, например) были выловлены на стадии компиляции.


> Что касается заполнения - в варианте с const или var параметром
> в классе-генераторе события объявляется нужная запись и
> заполняется - это более удобно, так как есть типизация,
> в отличие от Variant.


Я не совсем понимаю, так как в случае с Variant приведение типов контролируется также во время выполнения, а в случае с const или var параметрами приведение типов остается на совести программиста.


> Но сущесвенный недостаток: необходимость добавления пустого
> метода в каждый класс при появлении нового события.


На паттерн Visitor не хочешь посмотреть ?


 
GuAV ©   (2005-11-23 17:15) [5]


> Я бы использовал именно Enumeration, чтобы возможные
> ошибки (опечатки, например) были выловлены на стадии
> компиляции.


Согласен, однако целочисленные константы с осмысленными идентификаторами снижают риск опечатки так же хорошо :)
При этом, можно задать тип поддиапазона TObjMessage = OM_FIRST..OM_LAST, и использовать его.


> Я не совсем понимаю, так как в случае с Variant
> приведение типов контролируется также во время
> выполнения, а в случае с const или var параметрами
> приведение типов остается на совести программиста.


Ошибки типа "вызванно не то событие" в любом случае недопустимы и не контролируются. И в обоих случаях обработчик (молча или с исключением, но) проглотит не его данные (ещё неизвестно что хуже, получить там AV, или привести catption равный 2 к double, второе ловить сложнее).

Ошибки типа не тот параметр не на том месте (и в обработчике и в вызывающем) легче исключить именно в случае с записью.

И потом, Sender: TObject, Sender: TMapPolygon, Sender: TList - это классы, как их в вариант пихать ?


> На паттерн Visitor не хочешь посмотреть ?

Пошел искать. Спасибо.


 
GuAV ©   (2005-11-23 17:30) [6]


> > На паттерн Visitor не хочешь посмотреть ?


Насколько я понял он позволяет добавить метод при фиксированной иерархии классов. У меня никакая не иерархия, просто куча разных классов, и, что является проблемой, рост их числа и рост числа методов одинаково ожидается.


 
Игорь Шевченко ©   (2005-11-23 17:38) [7]


> И потом, Sender: TObject, Sender: TMapPolygon, Sender: TList
> - это классы, как их в вариант пихать ?


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


 
GuAV ©   (2005-11-23 21:11) [8]

С сообщениями ничего не вышло. При вызове диспача из себя же начинаются различные странности, вроде из цикла for считает выше границы (от этого спасла обёртка PUSHA/POPA вокруг вызова Dispatch ! хотя другого asm-кода нет, абсолютно везде модель register !), и другие странности, например колонки в гридах съезжают. Не исключаю кривизну своих рук, но похоже на глюк RTL.

Пока оставил как есть. С delphi сообщениями больше не связываюсь. К рассмотернию вариант jack128.


 
Leonid Troyanovsky ©   (2005-11-24 08:40) [9]


> GuAV ©   (23.11.05 21:11) [8]

> Пока оставил как есть. С delphi сообщениями больше не связываюсь.



С сообщениями все в порядке, дело в твоей реализации.

А, вообще-то, сама исходная постановка хромает на обе ноги.
Т.е., либо нужна модель, основанная на TComponent with Actions, либо
все классы д.б. собс-ручные, с тем самым интерфейсом, который  будет
вызываться по списку.
Но, и в последнем случае, TComponent предпочтительней, бо уже обладает
механизмом Notification, позволяющим оный список обновлять.

--
Regards, LVT.


 
Владислав ©   (2005-11-24 09:03) [10]

А если кинуть взгляд на 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:04) [11]

Уважаемые модераторы, удалите предыдущий пост пожалуйста. Забыл про форматирование :(

А если кинуть взгляд на 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:16) [12]

Продолжение...

Какой-то класс должен уведомлять о своих изменениях.

Вот простой пример реализации:

// идентификаторы событий:

 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:21) [13]

// Подписка на события клиентом.

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;


 
GuAV ©   (2005-11-24 15:06) [14]

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] последняя строка, обратный цикл и в Вашей реализации будет лучше.


 
GuAV ©   (2005-11-24 15:08) [15]

Сейчас используется вариант 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:40) [16]

GuAV ©   (24.11.05 15:08) [15]


>  EventsList.Remove(Self);

EventsList.Extract(Self);
т.к. я перешел на владеющий TObjectList.


 
Leonid Troyanovsky ©   (2005-11-24 15:47) [17]


> GuAV ©   (24.11.05 15:06) [14]

> Пожалуйста, если можно, подробнее. Дело в том, что, как
> сейчас уже выяснилось все "подписчики" являются компонентами,
>  ActionList-ы используются. Но я так и не понял, как создать
> свой Aсtion со своими параметрами и запускать его.


Имелось ввиду, что нужна реализация Actions for TComponent,
бо Actions for TWincontrol расчитана на видимые контролы.
Ну, а реализацию можно подсмотреть в том же VCL.

А если уж выяснилось, что это компоненты, то, по-крайней мере,
необходимо использовать FreeNotification для обновления списка.

--
Regards, LVT.


 
GuAV ©   (2005-11-24 16:10) [18]

Leonid Troyanovsky ©   (24.11.05 8:40) [9]


>либо
> все классы д.б. собс-ручные, с тем самым интерфейсом,
>который  будет
>вызываться по списку.
> Но, и в последнем случае, TComponent предпочтительней,
>бо уже обладает
> механизмом Notification, позволяющим оный список
> обновлять.

Один интерфейс на все события не годится. Т.к. добавление новых событий будет связано с изменением этого интерфейса. Можно завести для каждого события (или для небольших групп событий) свой интерфейс. Тогда при вызове события диспечтер запрашивает у каждого подписчика интерфейс события и вызывает его при его наличии.
Однако, это ничем не лучше варианта [15], т.к. всё равно несколько циклов.

Вообще говоря, чтобы обеспечить единый цикл рассылки требуется ввести единый тип параметра события - в любом случае (ассемблер и манипуляция с моделями вызовов неприемлимы, при том и они вряд ли бы помогли). Это уже можно сделать при варианте [15], передавая в каждое событие один var параметр (а для цикла расылки он будет нетипизированным var параметром). Я посмотрю, стоит ли.

Notification не нужен, желательна подписка и отписка вручную.


 
Leonid Troyanovsky ©   (2005-11-24 16:14) [19]


> 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.


 
Leonid Troyanovsky ©   (2005-11-24 16:23) [20]


> GuAV ©   (24.11.05 16:10) [18]

> Один интерфейс на все события не годится. Т.к. добавление
> новых событий будет связано с


Если внимательно и мучительно размышлять, то окажется, что
интерфейса TNotifyEvent вполне хватит для своих событий, т.е.,
для тех, что происходят в собственном приложении.
Просто, это самый распространенный обработчик (а откуда вдруг
возьмутся другие?).

Для взаимодействия с другими приложениями есть другие решения,
типа издатель-подписчик (см пример в demos).

Ну, а по поводу Notification - это должно быть правилом при любом
использовании ссылок на другие компоненты .

--
Regards, LVT.


 
Владислав ©   (2005-11-24 16:33) [21]


> Владислав ©   (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] последняя строка, обратный цикл
> и в Вашей реализации будет лучше.


Замечание здравое. Спасибо. Я об этом не задумывался.


 
GuAV ©   (2005-11-24 16:50) [22]

Про реакцию на 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), или делать нотификацию вручную.


 
GuAV ©   (2005-11-24 17:06) [23]

Владислав ©   (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;



 
Владислав ©   (2005-11-24 17:22) [24]

function Advise(AEventNotification: TEventNotification;
   AcceptedEvents: array of TClass; // 1).
   RaiseExceptionIfUnaccepted: Boolean = False // 2).
): Integer; overload;


1). Я именно это и пытался обойти. Зависимость между классами.
2). А хорошо ли, если остальные подписчики событие не получат?


 
GuAV ©   (2005-11-24 17:38) [25]


> 2). А хорошо ли, если остальные подписчики событие не
> получат?

Исключение может быть поднято после рассылки и содержать информацию обо всех подписчиках, не принявших свои сообщения.


> 1). Я именно это и пытался обойти. Зависимость между
> классами.

Тогда поясните мысль, что значит "зависимость между классами". Подписчик в любом случае должне знать что за класс он принимает.

Если речь идёт об игнорировании старых классов, nо исключении при вновь появившихся, то возможно  определить константу EmptyEventNotification (пустой классовый метод) и регистрировать её так
Advise(EmptyEventNotification,
 [все, известные, приёмнику, события], True).

Альтернатива Advise была предложена для возможности избежания того самого case с is. Можно задать различные методы для различных классов событий, при этом в параметре уже правильный класс, а приводить при Advise.


 
Владислав ©   (2005-11-24 17:56) [26]


> Тогда поясните мысль, что значит "зависимость между классами".
>  Подписчик в любом случае должне знать что за класс он принимает.


Должен то он должен. Но при добавлении нового класса события в издателя, подписчики, которые не изменялись, начнут выкидывать исключения? А может новое добавленное событие им совсем не нужно.


> Если речь идёт об игнорировании старых классов, nо исключении
> при вновь появившихся, то возможно  определить константу
> EmptyEventNotification (пустой классовый метод) и регистрировать
> её так
> Advise(EmptyEventNotification,
>  [все, известные, приёмнику, события], True).
>
> Альтернатива Advise была предложена для возможности избежания
> того самого case с is. Можно задать различные методы для
> различных классов событий, при этом в параметре уже правильный
> класс, а приводить при Advise.


Не совсем понял эту мысль.


 
Leonid Troyanovsky ©   (2005-11-24 18:13) [27]


> GuAV ©   (24.11.05 16:50) [22]

> Как правильно запустить диспетчеризацию Action, что делает
> ActionLink ?


После некоторого раздумья пришел к выводу, что распространение
Actions на TComponent не имеет особого смысла, т.к. требует изменений
базового класса, что, собс-но, нужно было б избежать.


> Вот например событие FOnCaptionChanged: procedure(Sender:
>  TObject; const Old, New: string) of object; . Где получатель
> возьмёт старый Caption ?


Я думаю, что Sender их знать должен.

--
Regards, LVT.


 
GuAV ©   (2005-11-24 18:33) [28]

Владислав ©   (24.11.05 17:56) [26]

Тогда противоречие. С одной стороны, Вы хотите поднять исключение при неожиданном событии:

Владислав ©   (24.11.05 9:03) [21]

> Приходится проверять и приводить тип события. Здесь,
> чтобы избежать долгих часов поиска, почему же не
> приходит уведомление после некоторых изменений
> классов, нужно наверное генерировать исключение, если
> получили не тот тип, который ожидали.


С другой стороны, искличение не требуется:

Владислав ©   (24.11.05 17:56) [26]

>  Но при добавлении нового класса события в издателя,
> подписчики, которые не изменялись, начнут выкидывать
> исключения? А может новое добавленное событие им
> совсем не нужно.


Код из [25] приводит к исключению при новом для объекта событии.

-----

Вообще, не вижу проблемы, т.к. изменение класса события будут видны всем, а имя класса и идентификатор менять вроде как незачем.

Поясняю
GuAV ©   (24.11.05 17:38) [25]

> Альтернатива Advise была предложена для возможности
> избежания того самого case с is. Можно задать
> различные методы для различных классов событий, при
> этом в параметре уже правильный класс, а приводить при
> Advise.


У меня класс имеющий два обработчика
 procedure OnEvent1(const AEventObject: TEventObject) of object;
 procedure OnEvent2(const AEventObject: TEventObject) of object;

Я их регистрирую:
 Advise(OnEvent1, [TEvent1]): Integer; overload;
 Advise(OnEvent2, [TEvent2]): Integer; overload;


К сожалению
>при
> этом в параметре уже правильный класс, а приводить при
> Advise.

не удасться, синтаксис не позволяет.

Вместо различия по классам, можно было бы ввести различие по EventID.
Однако, я считаю, что классов вполне достаточно и на каждое событие следует завести свой класс:

type
 TEvent1 = class(TEventObject)
 end;

 TEvent2 = class(TEventObject)
 end;


 
GuAV ©   (2005-11-24 18:46) [29]


> После некоторого раздумья пришел к выводу, что
>распространение
> Actions на TComponent не имеет особого смысла, т.к.
>требует изменений
> базового класса, что, собс-но, нужно было б избежать.

Как раз с этим проблем нет. Все подписчики - компоненты. Все actions дисптчеризируются всем компонентам (иначе как статусбар получает новый хинт), при этом кому не надо, игнорирует (например TXPMainfest).
Речь идёт не об неизменности базового класса при необходимости новой обработки (что тоже достигается, код TComponent.ExecuteAction вызывает Action.ExecuteTarget), а об неизменности классов, которым событие не нужно.


> Я думаю, что Sender их знать должен.

Если бы, у меня был бы один тип события. В моих событиях параметр Old - единственная копия старого значения (параметр New действительно избыточен, но уже так есть).


 
Владислав ©   (2005-11-25 09:24) [30]


> GuAV ©   (24.11.05 18:33) [28]
>
> Тогда противоречие. С одной стороны, Вы хотите поднять исключение
> при неожиданном событии:


Нет противоречия. Здесь речь о разных вещах.


// Пример № 1
// Например, издатель объявляет событие:
 EVENTID_SOME_EVENT = 1;
// Класс для него
 TSomeEvent = class(TEventObject)
...
 end;



// Пример № 1
// В подписчике есть обработчик
...
 case AEventObject.EventID of
   EVENTID_SOME_EVENT:
     SomeEventHandler(AEventObject as TSomeEvent); // вот это исключение мне нужно.
// Если при EventID = EVENTID_SOME_EVENT класс объекта события не TSomeEvent - это плохо. Это ошибка.


Дальше... Все течет, все изменяется. Система усложнилась. В издателе добавилось новое событие.


// Пример № 1
 EVENTID_NEW_EVENT = 2

 TNewEvent = class(TEventObject)
...
 end;


Ну и добавилось, ну и слава богу. Возможно какому-то из подписчиков такое событие понадобилось. Вот такого подписчика и нужно научить обрабатывать новое событие. А все остальные должны продолжать работать без изменений. Т.е. обработчик в примере № 2 должен остаться работоспособным без переписывания его кода.

Надеюсь, понятно изложил.


 
GuAV ©   (2005-11-25 12:00) [31]

Владислав ©   (25.11.05 9:24) [30]


>  SomeEventHandler(AEventObject as TSomeEvent); // вот
> это исключение мне нужно
> // Если при EventID = EVENTID_SOME_EVENT класс объекта
> события не TSomeEvent - это плохо. Это ошибка.

Каким образом это может оказаться ?

И я всё же предлагаю для каждого события писать свой класс и различать события именно по классам.

Думаю, если оставлять как есть, наилучшим вариантом будет проверка уже в обработчике, как в Вашем примере. Проверку в  TEventConnectionPoint можно осуществлять так:

 proceudre TEventConnectionPoint.AddIdChecking(Event: TClass; ID: Integer);
 
 proceudre TEventConnectionPoint.DoIdChecking(Event: TEventObject)


Исключение будет выброшенно, если ID в списке упоминается, но ни разу с этим Event


 
Владислав ©   (2005-11-25 12:53) [32]


> Каким образом это может оказаться ?


Человеку (программисту) свойственно ошибаться.


> И я всё же предлагаю для каждого события писать свой класс
> и различать события именно по классам.


Слишком накладно для наращивания, мне кажется. Да и если обработчик таки один, то какая разница? Проверка типов нужна будет.


>  proceudre TEventConnectionPoint.AddIdChecking(Event: TClass;
>  ID: Integer);
>  
>  proceudre TEventConnectionPoint.DoIdChecking(Event: TEventObject)


Это дополнительный код, который должен вызывать подписчик.
А вот это не проще написать AEventObject as TSomeEvent? ;)


 
Leonid Troyanovsky ©   (2005-11-25 14:39) [33]


> GuAV ©   (24.11.05 18:46) [29]


> Речь идёт не об неизменности базового класса при необходимости
> новой обработки (что тоже достигается, код TComponent.ExecuteAction
> вызывает Action.ExecuteTarget), а об неизменности классов,
>  которым событие не нужно.

TComponent.ExecuteAction д.б. override, т.е., для уже существуещего
компонента это не пройдет. Ну, или, нет даже у TControl того самого
поля, которое можно было задействовать для этой цели.

> > Я думаю, что Sender их знать должен.

> Если бы, у меня был бы один тип события. В моих событиях
> параметр Old - единственная копия старого значения (параметр
> New действительно избыточен, но уже так есть).

Ну, хорошо, пусть Sender  будет промежуточным компонентом,
определенного класса, известного получателю.
Т.е., там будут как поля для записи, так и для чтения.
А после вызова (отправки компонента получателю)
отправляющий может проанализировать изменения.

Ну, а получатели будут выбирать из посылок лишь те объекты,
в обработке которых они заинтересованы (которым обучены).

Хотя, если вся эта модель направлена на согласование поведения
(состояния) группы контролов, то я бы избрал немного другой путь.

--
Regards, LVT.


 
GuAV ©   (2005-11-25 15:05) [34]

Владислав ©   (25.11.05 12:53) [32]

> А вот это не проще написать AEventObject as
> TSomeEvent? ;)

Проще.

---
Спасибо за идею.
Но я при её реализации всё наверное же буду делать несколько обработчиков и без EventID, с определнием обработчика по классу события.

Leonid Troyanovsky ©   (25.11.05 14:39) [33]

> TComponent.ExecuteAction д.б. override, т.е., для уже
>существуещего
> компонента это не пройдет.

Не обязательно. Вот например Edit ничего не знает о TEditAction и его наследнике TEditCopy (доказано GREPом), а они есть:

procedure TEditCopy.ExecuteTarget(Target: TObject);
begin
 GetControl(Target).CopyToClipboard;
end;

function TEditAction.GetControl(Target: TObject): TCustomEdit;
begin
 { We could hard cast Target as a TCustomEdit since HandlesTarget "should" be
   called before ExecuteTarget and UpdateTarget, however, we"re being safe. }
 Result := Target as TCustomEdit;
end;

function TEditAction.HandlesTarget(Target: TObject): Boolean;
begin
 Result := ((Control <> nil) and (Target = Control) or
   (Control = nil) and (Target is TCustomEdit)) and   TCustomEdit(Target).Focused;
end;



> Хотя, если вся эта модель направлена на согласование
>поведения
> (состояния) группы контролов, то я бы избрал немного
> другой путь.

Частично согласование группы контролов (тут реально нужен или Sender или ничего, хотя в существующем коде юзается новый параметр), частично перерисовка PaintBox"ов (старый и новый параметры для рассчёта региона перерисовки). Какой путь ?


 
GuAV ©   (2005-11-25 15:09) [35]

Владислав ©   (25.11.05 12:53) [32]

> Слишком накладно для наращивания, мне кажется. Да и
> если обработчик таки один, то какая разница? Проверка
> типов нужна будет.

Никакой разницы, добавить Id или пустой класс-потомок. Можно взять за правило делать событие пустым классом-наследником какого либо класса и накладности не будет.

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


 
Владислав ©   (2005-11-25 15:46) [36]


> Никакой разницы, добавить Id или пустой класс-потомок. Можно
> взять за правило делать событие пустым классом-наследником
> какого либо класса и накладности не будет.


Ну либо я опять не понял Вашу идею, либо разница есть. Может быть она не так ощутима. В моем варианте подписчик получит уведомление без изменения обработчика, после добавления нового события ;) Хотя это уже мелочи.
Впрочем, у нас задачи разные, так что я перестаю советовать :)


 
Leonid Troyanovsky ©   (2005-11-25 16:10) [37]


> GuAV ©   (25.11.05 15:05) [34]

> Не обязательно. Вот например Edit ничего не знает о TEditAction
> и его наследнике TEditCopy


Да с TWinControl проблем-то и нет, бо для них Actions и сделаны.
А уже у TControl тех полей (которые можно б было задействовать
уже нет). Это и понятно, ведь их назначение - согласование контролов,
отвечающих за пользовательский ввод, ну, а о своем содержимом
винконтролы пусть заботятся сами.


> Частично согласование группы контролов (тут реально нужен
> или Sender или ничего, хотя в существующем коде юзается
> новый параметр), частично перерисовка PaintBox"ов (старый
> и новый параметры для рассчёта региона перерисовки). Какой
> путь ?


Когда-то давно, когда еще не было никаких Actons люди пользовались
таким приемом (пример) : для согласования состояния меню,
кнопок и т.п. с дублирующей функциональностью делалось некое
свойство у, например, содержащей их формы.
Это свойсто может быть set или нечто более сложное, но главным
для этого свойста были побочные эффекты на, например, на write,
в котором устанавливались нужные состояния затронутых изменением
контролов.
Т.е., для того, чтобы, например, сделать (не)доступным все контролы,
относящиеся к чему-либо, достаточно в обработчике события этого
контрола (методе формы) изменить нужным образом это свойство.

Если попытаться сказать кратко, то это проблема относится не
столько к самим контролам, а к их контейнеру, и решаться должна
не модификацией контролов, а модификацией контейнера, для
обеспечения требуемого поведения.

--
Regards, LVT.


 
GuAV ©   (2005-11-25 16:25) [38]


> В моем варианте подписчик получит уведомление без
> изменения обработчика, после добавления нового события
> ;)

А зачем оно ему нужно, если он не знает что делать ?

Разница есть, но ИМХО оба варианта имеют право на существование, и даже могут быть реализованы одним диспетчером.


> так что я перестаю советовать :)

Собственно идею Вашу я уже понял, однако действительно интерес чисто теоретический, на будущее. Удобство внедрения - хорошее преимущество, но в моём случае уже всё внедрено :)


 
GuAV ©   (2005-11-25 16:40) [39]

Leonid Troyanovsky ©   (25.11.05 16:10) [37]

У меня разные формы, фреймы. Контейнером для всех, разумеется является Application, однако в VCL проектах наследника Application не пишут.

Делать одну из форм диспетчером - неудобно. Поэтому отдельный класс диспетчера. Более того, приёмниками событий изначально были не только контролы, но и классы в некоторых списках, это  уже переделано.

Но зачем прописывать каждый контрол, если он может прописать себя сам, тогда это будет в его модуле, а не в модуль диспетчера.

С контролами одного модуля у меня именно так, через
св-ва контейнера.


 
Владислав ©   (2005-11-25 16:45) [40]


> А зачем оно ему нужно, если он не знает что делать ?


Ну кроме case еще есть if... Может быть группа событий :)


if
 (AEventObject.EventID >= EVENTID_SOME_FIRST) and
 (AEventObject.EventID <= EVENTID_SOME_LAST)
then
 ReloadSomeProperties(AEventObject);



Страницы: 1 2 вся ветка

Форум: "Основная";
Текущий архив: 2005.12.25;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.71 MB
Время: 0.013 c
4-1130266042
zaN0za
2005-10-25 22:47
2005.12.25
возможно ли такое преобразование?


2-1134125888
kvorubin
2005-12-09 13:58
2005.12.25
Мужики, как сравнить 2 списка со словами???


2-1134041352
De1uxe
2005-12-08 14:29
2005.12.25
Real -> integer


2-1134231266
vasek
2005-12-10 19:14
2005.12.25
Создал базу данных в IBExpert. В базе данных 3-и таблицы:


2-1134313083
The Only
2005-12-11 17:58
2005.12.25
проблемы с меню





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский