unit tw39582; {$mode Delphi}{$H+} // примитивный множественный обрабочтчик событий, Pub/Sub. // // универсальный класс можно найти, например, в Spring4Delphi // но там очень сложная библиотека с вуду типа генерации кода // в рантайме по RTTI, также с расширенными контейнерными // библиотеками и спользованием генериков так глубоко, что // Delphi XE2 часто ломается на сборке interface uses Classes, SysUtils, Generics.Collections; // события только типа procedure (Sender: TObject; ... params ...; var Cancel: boolean) of object // другие типы не поддерживаются, тип не проверяется (или S4D со всеми наворотами) // параметры событий хранятся внутри объекта во время цикла // следовательно, никакой рекурсии и многопоточности // процедуры вызова и передачи параметров придётся хардкодить и копипастить (или S4D с их вуду) type TSimpleMultiEvent

= class; ISimpleMultiEvent

= interface procedure AddHandler(const Proc: P); procedure RemoveHandler(const Proc: P); overload; procedure RemoveHandler(const Obj: TObject); overload; procedure RemoveHandler(const Method: TMethod); overload; // procedure RunEventOnBorrowedData(const RootEvent: TSimpleMultiEvent

); -- FPC bug 39681 procedure RunEventOnBorrowedData(const RootEvent: TObject); end; ESimpleMultiEvent = class(Exception) end; { TSimpleMultiEvent } TSimpleMultiEvent

= class(TInterfacedObject, ISimpleMultiEvent

) protected // for debug tests if nothing better // 0 - default; +1, +2 - вложенные вызовы при запуске события; -1, -2 - при удалении/добавлении обработчиков MEState: integer; MEHandlers: TList

; type TPredicate = function(var Value; var Data): boolean of object; private function PredMeth(var Handler; var Data): Boolean; function PredObj (var Handler; var Data): Boolean; function PredType(var Handler; var Data): Boolean; protected // переменные события EV_Sender: TObject; EV_StopEventLoop: boolean; // цикл отработки событий после сохранения переменных; // возвращает количество вызванных обработчиков function InvokeAllHandlers: integer; // вызывает один обработчик, с нужными параметрами и сохраняет флаг остановки цикла procedure InvokeOneHandler(const Handler: P); virtual; abstract; // забирает данные из "корневого" события procedure BorrowDataFrom(const RootEvent: TSimpleMultiEvent

); virtual; // вызывается после обработчиков - зачистка ref-counted переменных procedure ClearEventVars; virtual; // дополнительные функции, например "показать часики" перед обработчиками // события и убрать после, или записать в журнал или еще что. // не должны бросать наружу исключения! procedure BeforeEvents; virtual; procedure AfterEvents; virtual; // FPC bug 39581, param should be TSimpleMultiEvent

procedure RunEventOnBorrowedData(const RootEvent: TObject); private procedure ClearAllHandlers; procedure InternalRemoveHandlers(const Where: TPredicate; var Data); procedure StartDoingEvent; procedure EndDoingEvent; procedure StartManagingHandlers; procedure EndManagingHandlers; public procedure AddHandler(const Proc: P); procedure RemoveHandler(const Proc: P); overload; procedure RemoveHandler(const Obj: TObject); overload; procedure RemoveHandler(const Method: TMethod); overload; procedure BeforeDestruction; override; procedure AfterConstruction; override; var EventAfter, EventBefore: ISimpleMultiEvent

; // procedure RunEvent(Self, ......) - копипастить в каждом классе end; IMultiNotifyEvent = interface(ISimpleMultiEvent) function RunEvent(const Sender: TObject): integer; end; TMultiNotifyEvent = class (TSimpleMultiEvent, IMultiNotifyEvent) protected procedure InvokeOneHandler(const Handler: TNotifyEvent); override; function RunEvent(const Sender: TObject): integer; end; TSingleEvent = procedure (const NewValue: single) of object; IMultiSingleValEvent = interface(ISimpleMultiEvent) function RunEvent(const NewVal: single): integer; end; { TMultiSingleValEvent } TMultiSingleValEvent = class (TSimpleMultiEvent, IMultiSingleValEvent) private EV_Val: single; protected procedure InvokeOneHandler(const Handler: TSingleEvent); override; function RunEvent(const NewVal: single): integer; end; implementation type TMethodEq = record helper for TMethod function EqualTo(const M2: TMethod): boolean; inline; end; function TMethodEq.EqualTo(const M2: TMethod): boolean; begin Result := (Self.Data = M2.Data) and (Self.Code = M2.Code); end; { TSimpleMultiEvent

} // вызывается после сохранения всех параметров события function TSimpleMultiEvent

.InvokeAllHandlers: integer; var Handler: P; begin StartDoingEvent; try Result := 0; EV_StopEventLoop := False; for Handler in MEHandlers do begin InvokeOneHandler(Handler); Inc(Result); if EV_StopEventLoop then break; // событие отработано, дальше не вызываем end; finally try EndDoingEvent; finally ClearEventVars; end; end; end; procedure TSimpleMultiEvent

.BorrowDataFrom(const RootEvent: TSimpleMultiEvent

); begin Self.EV_Sender := RootEvent.EV_Sender; end; procedure TSimpleMultiEvent

.ClearEventVars; begin EV_StopEventLoop := False; // готовимся к следующему циклу EV_Sender := nil; end; procedure TSimpleMultiEvent

.BeforeEvents; begin // to be used by derived subclasses end; procedure TSimpleMultiEvent

.AfterEvents; begin // to be used by derived subclasses if Assigned(EventAfter) then // EventAfter.Run end; procedure TSimpleMultiEvent

.RunEventOnBorrowedData( const RootEvent: TObject); begin BorrowDataFrom(RootEvent as TSimpleMultiEvent

); InvokeAllHandlers; end; // Inc/Dec можно заменить на многопоточный System.SyncObjs.TInterlocked.Increment procedure TSimpleMultiEvent

.StartDoingEvent; begin // >0 тоже нельзя, переменные сохраняются - не реентерабельно! if MEState <> 0 then raise ESimpleMultiEvent.Create('Can not invoke ' + QualifiedClassName + ' while its handlers are changing.'); Inc(MEState); BeforeEvents; end; procedure TSimpleMultiEvent

.EndDoingEvent; begin if MEState > 0 then Dec(MEState); if MEState = 0 then AfterEvents; end; procedure TSimpleMultiEvent

.StartManagingHandlers; begin if MEState > 0 then raise ESimpleMultiEvent.Create('Can not change handlers of ' + QualifiedClassName + ' while the event is running.'); Dec(MEState); end; procedure TSimpleMultiEvent

.EndManagingHandlers; begin if MEState < 0 then Inc(MEState); end; procedure TSimpleMultiEvent

.ClearAllHandlers; begin StartManagingHandlers; try if MEHandlers <> nil then MEHandlers.Clear; finally EndManagingHandlers end; end; (*** возникает вопрос, как передать значение. var M: TNotifyEvent absolute Proc; - проще всего и быстрее всего, но... [DCC Fatal Error] F2084 Internal Error: AV084D2278-W00000014-1 /*union*/ record case... 0:(Method: procedure of object); 1:(Arg: P); end; [DCC Error] E2569 Type parameter 'P' may need finalization - not allowed in variant record. Consider using RECORD constraint Приходится делать Move, и даже там "хардкодить" размер переменной :-/ ***) procedure TSimpleMultiEvent

.AddHandler(const Proc: P); var M: procedure of object; // const Sz = SizeOf(M) == 0 !!! expression типа результат вызова void-функции; begin StartManagingHandlers; try M := nil; Move(Proc, M, SizeOf(TNotifyEvent)); if Assigned(M) then MEHandlers.Add(Proc); finally EndManagingHandlers end; end; procedure TSimpleMultiEvent

.InternalRemoveHandlers( const Where: TPredicate; var Data); var i: integer; H: P; begin if MEHandlers = nil then exit; i := MEHandlers.Count; if i <= 0 then exit; StartManagingHandlers; try while i > 0 do begin Dec(i); H := MEHandlers[i]; if Where(H, Data) then MEHandlers.Delete(i); end; finally EndManagingHandlers end; end; function TSimpleMultiEvent

.PredMeth(var Handler; var Data): Boolean; var M: TMethod absolute Handler; // - ICE on "absolute P"! N: TMethod absolute Data; begin // Move(Handler, M, SizeOf(TNotifyEvent)); Result := ( M.EqualTo( N ) ); end; procedure TSimpleMultiEvent

.RemoveHandler(const Method: TMethod); var LMethod: TMethod; // generic lambdas can not capture const-param record begin StartManagingHandlers; try LMethod := Method; InternalRemoveHandlers( PredMeth, LMethod ); finally EndManagingHandlers end; end; function TSimpleMultiEvent

.PredObj(var Handler; var Data): Boolean; var M: ^TMethod; Obj: TObject absolute Data; begin M := Pointer(@Handler); Result := M^.Data = Obj; end; procedure TSimpleMultiEvent

.RemoveHandler(const Obj: TObject); var LObj: TObject; begin StartManagingHandlers; try LObj := Obj; InternalRemoveHandlers( PredObj, LObj ); finally EndManagingHandlers end; end; function TSimpleMultiEvent

.PredType(var Handler; var Data): Boolean; var MH: TMethod absolute Handler; MD: TMethod absolute Data; begin // Move(Handler, MH, SizeOf(TNotifyEvent)); Result := MH.EqualTo(MD); end; procedure TSimpleMultiEvent

.RemoveHandler(const Proc: P); var MP: TMethod; // generic lambdas can not capture const-param record begin StartManagingHandlers; try MP.Code := nil; Move(Proc, MP, SizeOf(TNotifyEvent)); InternalRemoveHandlers( PredType, MP ); finally EndManagingHandlers end; end; procedure TSimpleMultiEvent

.BeforeDestruction; begin Assert( MEState = 0, 'MultiEvent is busy, can not destroy!' ); ClearAllHandlers; ClearEventVars; MEHandlers.Free; inherited; end; procedure TSimpleMultiEvent

.AfterConstruction; begin MEHandlers := TList

.Create; MEHandlers.Capacity := 4; inherited; end; { TMultiNotifyEvent } procedure TMultiNotifyEvent.InvokeOneHandler(const Handler: TNotifyEvent); begin Handler(EV_Sender); end; function TMultiNotifyEvent.RunEvent(const Sender: TObject): integer; begin EV_Sender := Sender; Result := InvokeAllHandlers; end; { TMultiSingleValEvent } procedure TMultiSingleValEvent.InvokeOneHandler(const Handler: TSingleEvent); begin Handler( EV_Val ); end; function TMultiSingleValEvent.RunEvent(const NewVal: single): integer; begin EV_Val := NewVal; Result := InvokeAllHandlers; end; { TMethodEq } (**** // а вот тут копи-паста для использования type iMultiEventExample = interface(iSimpleMultiEvent) function RunEvent(const Sender: TObject): integer; end; TMultiEventExample = class(TSimpleMultiEvent, iMultiEventExample) public function RunEvent(const Sender: TObject): integer; procedure InvokeOneHandler(const Handler: TNotifyEvent); override; end; // // использовать - через интерфейс // THostingComponent = class .... // private MyEvent: iMultiEventExample; // // // чтобы другие объекты могли подписываться // public property Event: iMultiEventExample read MyEvent; // end; // // procedure THostingComponent.DoEvent; begin // MyEvent.RunEvent( Self, ....); // end. { TMultiEventExample } function TMultiEventExample.RunEvent(const Sender: TObject): integer; begin EV_Sender := Sender; Result := InvokeAllHandlers(); end; procedure TMultiEventExample.InvokeOneHandler(const Handler: TNotifyEvent); begin Handler(EV_Sender); end; // ищем у подписантов строку типа Apple=20, возвращаем значение type TSomeSearchEvent = procedure (const Sender: TObject; const Name: string; var Data: integer; Var Found_Stop_Now: boolean ) of object; iMEvExample2 = interface(iSimpleMultiEvent) function RunEvent(const Sender: TObject; const Name: string; var Data: integer): integer; end; TMEvExample2 = class(TSimpleMultiEvent, iMEvExample2) private EV_Data: ^integer; EV_Name: string; protected procedure ClearEventVars; override; public function RunEvent(const Sender: TObject; const Name: string; var Data: integer): integer; procedure InvokeOneHandler(const Handler: TSomeSearchEvent); override; end; { TMEvExample2 } procedure TMEvExample2.InvokeOneHandler(const Handler: TSomeSearchEvent); begin Handler( EV_Sender, EV_Name, EV_Data^, EV_StopEventLoop ); end; procedure TMEvExample2.ClearEventVars; begin EV_Name := ''; // освободить ARC-Объект // EV_Data := nil; - перестраховка для отладки inherited; end; function TMEvExample2.RunEvent(const Sender: TObject; const Name: string; var Data: integer): integer; begin EV_Sender := Sender; EV_Data := @Data; EV_Name := Name; Result := InvokeAllHandlers(); end; (**** ***) end.