我最近在Cnblogs上看到有一些朋友在讨论关于事件侦听,他的想法是,给Delphi即有的事件,例如click, mouse加上多播机制,在我认为,这种做法是不可取的,delphi在设计之初,就是把所谓的事件当做是一个回调函数来处理,这就使得每个事件只能有一个响应者,在这之上给组件加上事件多播机制只会让代码更加难懂,更加难用,增加代码量。而且用这种方法增加的事件多播机制只能用于界面上, 不能很好的用于处理数理, 实现数据与界面的分离, 降低耦合度, 在此, 我把我的事件多播实现机制与原理分享给大家. 至于好与坏, 请在实践中进行检验.
与武稀松的实现方法相似, 本方法在实现上用到了以下几个知识点:
- RTTI的 TMethod 结构体, 该结构体可用于比较两个事件处理方法是否相同, 用于移除事件侦听.
- 用于了指针, 用于传递事件数据, 需要了解如何获取内存, 释放内存
- 泛型. 用于保存事件数据.
下面来具体分析 代码.
unit EventListener;
interface
uses System.Generics.Collections;
type
TNwEventProc = procedure(const EventType:string;EventData:Pointer) of object;
INiEventListener = interface
procedure PerformEvent(EventType: String; EventData: Pointer);
procedure removeEventListener(EventType: String; EventProc: TNwEventProc);
procedure addEventListener(EventType: String; EventPro: TNwEventProc);
end;
TEventData = record
EventType:String;
EventProc:TNwEventProc;
end;
TNTEventList = TList<TEventData>;
TNwEventListener = class(TInterfacedObject, INiEventListener)
protected
FEventList:TNTEventList;
public
procedure PerformEvent(EventType: String; EventData: Pointer);
procedure removeEventListener(EventType: String; EventProc: TNwEventProc);
procedure addEventListener(EventType: String; EventPro: TNwEventProc);
destructor Destroy; override;
constructor Create();
end;
TNwEventProc 是事件处方法, 有两个参数, 一个是事件类型EventType, 一个是事件数据 EventData. 事件类型是字符串, 用于自己定义事件. 事件数据是一个指针, 指向事件的数据所在内存. 这个内存需要你自己手动去管理.
INiEventListener 是事件处理器的接口, 它包括三个就去, 分别是执行事件PerformEvent, 添加事件 AddEventListener 和删除事件RemoveEventListener. 当我们需要在某一事件发生的时候处理某数据时, 就可以调用添加事件AddEventListener来增加某个事件的侦听, 当这个事件发生时, 相应的事件就会被处理. 如果你不想再这个事件发生时处理数据, 就需要调用删除事件RemoveEventListener来移除事件的侦听. 在使用时, 你可以使用一个全局的事件处理器来管理事件; 如果你只想对某一类增加事件处理功能, 这时, 你的类就需要继承自TNwEventListener, 该类已经为你实现了INiEventListener接口, 在需要让某一个事件触发时,你只需要调用PerformEvent就可以. 这个时候的事件数据就需要事先进行约定.
下面附上全部的事件处理器代码.
unit EventListener;
interface
uses System.Generics.Collections;
type
TNwEventProc = procedure(const EventType:string;EventData:Pointer) of object;
INiEventListener = interface
procedure PerformEvent(EventType: String; EventData: Pointer);
procedure removeEventListener(EventType: String; EventProc: TNwEventProc);
procedure addEventListener(EventType: String; EventPro: TNwEventProc);
end;
TEventData = record
EventType:String;
EventProc:TNwEventProc;
end;
TNTEventList = TList<TEventData>;
TNwEventListener = class(TInterfacedObject, INiEventListener)
protected
FEventList:TNTEventList;
public
procedure PerformEvent(EventType: String; EventData: Pointer);
procedure removeEventListener(EventType: String; EventProc: TNwEventProc);
procedure addEventListener(EventType: String; EventPro: TNwEventProc);
destructor Destroy; override;
constructor Create();
end;
var
IEventListener:INiEventListener;
implementation
uses System.SysUtils, Rtti;
{ TNwEventListener }
procedure TNwEventListener.addEventListener(EventType: String;
EventPro: TNwEventProc);
var
EventData:TEventData;
begin
EventData.EventType := EventType;
EventData.EventProc := EventPro;
FEventList.Add(EventData);
end;
constructor TNwEventListener.Create;
begin
inherited;
FEventList := TNTEventList.Create();
end;
destructor TNwEventListener.Destroy;
begin
FEventList.Clear;
FreeAndNil(FEventList);
inherited;
end;
procedure TNwEventListener.PerformEvent(EventType: String; EventData: Pointer);
var
I:Integer;
begin
for I := FEventList.Count - 1 downto 0 do
begin
if FEventList.Items[I].EventType = EventType then
FEventList.Items[I].EventProc(EventType, EventData);
end;
end;
procedure TNwEventListener.removeEventListener(EventType: String;
EventProc: TNwEventProc);
var
I: Integer;
EventData:TEventData;
ItemProc:TNwEventProc;
function SameMethod(S, T:TNwEventProc):Boolean;
var
MethodS, MethodT:TMethod;
begin
MethodS := TMethod(S);
MethodT := TMethod(T);
Result := ( MethodS.Code = MethodT.Code ) and ( MethodS.Data = MethodT.Data );
end;
begin
for I := FEventList.Count - 1 downto 0 do
begin
EventData := FEventList.Items[I];
ItemProc := EventData.EventProc;
if (EventData.EventType = EventType) and SameMethod(ItemProc, EventProc) then
begin
FEventList.Delete(I);
end;
end;
end;
end.
你可能会关心如何使用的问题, 下面,附上使用代码
//使用全局对象的方法
type
TUseEventListener = class
protected
procedure OnEvent( EventType:String; EventData:Pointer );
public
procedure InitEvents;
end;
var
UseEvent:TUseEventListener;
begin
{ TUseEventListener }
procedure TUseEventListener.InitEvents;
begin
IEventListener.addEventListener( 'OnMessage', OnEvent );
end;
procedure TUseEventListener.OnEvent( EventType:String; EventData: Pointer );
begin
ShowMessage( String( PChar( EventData ) ) );
end;
IEventListener := TNwEventListener.Create;
UseEvent := TUseEventListener.create;
UseEvent.InitListener;
IEventListener.PerformEvent( 'OnMessage', PChar( 'The Message should be shown.' ));
end.
使用类对象的方法:
const
evtDataChanged = 'evtDataChanged';
Type
PData = ^TData;
TData = record
Name: String;
City: String;
CellPhone: String;
Age: Integer;
end;
//数据处理类, 用于提供数据
TNwDataClass = class( TNwEventListener )
public
procedure AddData( Name, City, CellPhone:String; Age: Integer );
end;
//界面显示类
TNwInterface = class( TForm )
procedure FormCreate( Sender: TObject );
protected
procedure OnEvent( EventType:String; EventData:Pointer );
public
procedure AddDataToList( Data: TData );
end;
// TNwDataClass 应该有一个全局的实例, 用于提供数据. 在下面的代码中, 就以
// instanceDataClass 为这个实例
implementation
{ TNwDataClass }
procedure TNwDataClass.AddData( Name, City, CellPhone:String; Age: Integer );
var
Data: PData;
begin
//数据处理代码.
GetMem( Data, SizeOf( TData ) );
try
Data^.Name := Name;
Data^.City := City;
Data^.CellPhone := CellPhone;
Data^.Age := Age;
Self.PerformEvent( evtDataChanged, Data );
finally
FreeMem( Data );
end;
end;
{ TNwInterface }
procedure TNwInterface.FormCreate( Sender: TObject );
begin
instanceDataClass.addEventListener( evevtDataChanged, OnEvent );
end;
procedure TNwInterface.OnEvent( EventType:String; EventData: Pointer );
begin
AddDataToList( PData( EventData )^ );
end;
procedure TNwInterface.AddDataToList( Data: TData );
begin
//用于处理显示数据的代码.
end;
总体来看, 这种做法方便简单实用. 欢迎在使用过程中有什么问题与我交流!