利用RTTI实现Delphi的多播事件代理研究
我们知道Delphi的每个对象可以包含多个Property,Property中可以是方法,例如TButton.OnClick属性。Delphi提供的仅仅是
一对一的设置,无法直接让TButton.OnClick去调用多个方法,而Java中采用Listener模式有类似AddListener方法提供多播。
Delphi多播的思想源于Allen Bauer的Blog:http://blogs.embarcadero.com/abauer/2008/08/15/38865,
cnWizard的武稀松大侠在此思想基础上实现了Win32的Delphi多播机制见:http://www.raysoftware.cn/?p=44#comment-2442,并且应用于cnWizard;
开源项目DSharp实现了更加完整的多播机制,可提供基于接口的多播,见:https://bitbucket.org/sglienke/dsharp
本人希望借鉴前人的基础上,实现一个对象的事件多播代理,即TEventAgent是一个TObject的事件多播代理器,将一个TObject传给TEventAgent后, TEventAgent扫描TObject所有事件,并为每个事件提供多播功能。
下面程序是一个简单示例,引用了 DSharp.Core.Events.pas单元,并在Delphi XE3 测试成功.
unit utObjEventAgent; interface uses System.Generics.Collections, DSharp.Core.Events, System.TypInfo, Classes; type
TEventLinker=class(DSharp.Core.Events.TEvent) //单个事件的多播器
protected
FLinkedObject: TObject;
FLinkedProperty: PPropInfo;
FOriginal:TMethod; FEventTypeData:PTypeData;
FEventName:String;
procedure MethodAdded(const Method: TMethod); override;
procedure MethodRemoved(const Method: TMethod); override;
procedure Notify(Sender: TObject; const Item: TMethod;
Action: System.Generics.Collections.TCollectionNotification); override;
property Owner;
property RefCount;
public
constructor Create(LinkedObj:TObject; LinkedPrpt:PPropInfo);
destructor Destroy; override;
end; TEventAgent=class //对象的事件多播代理
protected
FOwner:TObject;
FPropList: PPropList;
FNameList:TDictionary<String, TEventLinker>;
procedure Prepare; virtual;
procedure Clear;
public
constructor Create(aOwner:TObject); virtual;
destructor Destroy;override;
function GetEventCount: Int32;
function GetEventName(Index: Int32): PWideChar;
procedure AddEventNotifier(EventName: String; const NotifierMethod: TMethod);overload; // 添加事件处理函数
procedure RemoveEventNotifier(EventName: String; const NotifierMethod: TMethod);overload; // 移除时间处理函数
end; implementation uses System.Rtti; { TEventLinker } constructor TEventLinker.Create(LinkedObj:TObject; LinkedPrpt:PPropInfo);
begin
inherited Create(LinkedPrpt.PropType^, nil);
FLinkedObject:=LinkedObj;
FLinkedProperty:=LinkedPrpt;
FEventName:=FLinkedProperty^.Name;
FOriginal:=GetMethodProp(FLinkedObject, FLinkedProperty);
SetMethodProp(FLinkedObject, FLinkedProperty, Self.GetInvoke);
if Assigned(FOriginal.Data) and Assigned(FOriginal.Code) then Add(FOriginal); //将原事件方法加入多播列表
end; destructor TEventLinker.Destroy;
begin
SetMethodProp(FLinkedObject, FLinkedProperty, FOriginal);
inherited;
end; procedure TEventLinker.MethodAdded(const Method: TMethod);
begin
end; procedure TEventLinker.MethodRemoved(const Method: TMethod);
begin
end; procedure TEventLinker.Notify(Sender: TObject; const Item: TMethod;
Action: System.Generics.Collections.TCollectionNotification);
begin
end; { TEventAgent } procedure TEventAgent.AddEventNotifier(EventName: String;
const NotifierMethod: TMethod);
var
V:TEventLinker;
begin
if FNameList.TryGetValue(EventName, V) then
begin
if V.IndexOf(NotifierMethod)< then
V.Add(NotifierMethod);
end;
end; procedure TEventAgent.Clear;
var
Item: TPair<String, TEventLinker>;
begin
for Item in FNameList do
Item.Value.Free;
FNameList.Clear;
if Assigned(FPropList) then FreeMem(FPropList);
end; constructor TEventAgent.Create(aOwner:TObject);
begin
inherited Create;
FNameList:=TDictionary<String, TEventLinker>.Create;
FOwner:=aOwner;
Prepare;
end; destructor TEventAgent.Destroy;
begin
Clear;
FNameList.Free;
inherited;
end; function TEventAgent.GetEventCount: Int32;
begin
Result:=FNameList.Count;
end; function TEventAgent.GetEventName(Index: Int32): PWideChar;
begin
Result:=PWideChar(FNameList.Keys.ToArray[Index]);
end; procedure TEventAgent.Prepare;
var
N, i:Integer;
Linker:TEventLinker;
Context: TRttiContext;
begin
Clear;
N:=GetPropList(FOwner.ClassInfo, FPropList);
for i := to N- do
if FPropList^[i].PropType^.Kind = tkMethod then
begin
if FPropList[i].GetProc=nil then Continue;
Linker:=TEventLinker.Create(FOwner, FPropList[i]);
Linker.FEventName:=FPropList[i].Name;
FNameList.Add(Linker.FEventName, Linker);
end;
end; procedure TEventAgent.RemoveEventNotifier(EventName: String;
const NotifierMethod: TMethod);
var
V:TEventLinker;
begin
if FNameList.TryGetValue(EventName, V) then
begin
V.Remove(NotifierMethod);
end;
end; end.
测试程序演示一个TButton被事件多播代理,其OnClick,OnMouseDown均有3个多播方法。
测试程序:
unit Unit1; interface uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, utObjEventAgent, DSharp.Core.Events, ObjAuto,
Vcl.StdCtrls; type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
procedure OnClick1(Sender:TObject);
procedure OnClick2(Sender:TObject);
procedure Button1MouseDown1(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure Button1MouseDown2(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
public
{ Public declarations }
FAgent:TEventAgent;
end; var
Form1: TForm1; implementation uses System.Rtti; {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.Add('Button1Click');
end; procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Memo1.Lines.Add(Format('Clicked at (%d, %d)', [X, Y]));
end; procedure TForm1.Button1MouseDown1(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Memo1.Lines.Add('Button1MouseDown1')
end; procedure TForm1.Button1MouseDown2(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Memo1.Lines.Add('Button1MouseDown2')
end; procedure TForm1.FormCreate(Sender: TObject);
var
V:TNotifyEvent;
M:TMouseEvent;
begin
FAgent:=TEventAgent.Create(Button1);
V:= Self.OnClick1;
FAgent.AddEventNotifier('OnClick', TMethod(V));
V:= Self.OnClick2;
FAgent.AddEventNotifier('OnClick', TMethod(V));
M:= Self.Button1MouseDown1;
FAgent.AddEventNotifier('OnMouseDown', TMethod(M));
M:= Self.Button1MouseDown2;
FAgent.AddEventNotifier('OnMouseDown', TMethod(M));
end; procedure TForm1.OnClick1(Sender: TObject);
begin
Memo1.Lines.Add('OnClick1');
end; procedure TForm1.OnClick2(Sender: TObject);
begin
Memo1.Lines.Add('OnClick2');
end; end.
测试程序dfm文件
object Form1: TForm1
Left =
Top =
Caption = 'Form1'
ClientHeight =
ClientWidth =
OnCreate = FormCreate
object Button1: TButton
Left =
Top =
Width =
Height =
Caption = 'Button1'
OnClick = Button1Click
OnMouseDown = Button1MouseDown
end
object Memo1: TMemo
Left =
Top =
Width =
Height =
Lines.Strings = (
'Memo1')
end
end
我的多播代理机制原理是,将所代理对象的所有事件指向代理器对应的函数,由此函数再以此调用多个回调函数。
1.当所代理事件没有任何事件回调时,多播代理不会修改事件函数指针,原对象此事件回调仍然为nil,
2.当所代理事件已经有事件回调函数指针,多播代理会将自己替换原函数指针,并且将原函数指针加入多播列表中.
我的多播机制有如下特点:
1.兼容Delphi的事件回调机制,因此对于老的程序,不用怎么修改,就能被回调多个函数,实现多播。
2.此多播机制不限于界面对象,可代理任何对象,只要此对象有放入public或published的事件property属性,均被自动代理,无所谓其传入的参数是什么类型及有多少个。
3.用户的对象如果需要多播功能,仅需要按照单个事件模式设计即可,多播代理自动帮他实现多播。
再举例1:
比如我们网络通讯假设用的是TTcpClient,从服务器接收数据。接收来的数据进行处理,处理过程有很多,比如有的模块需要存盘到文件,有的处理模块进行数据转发,有的模块需要进行解码分析。
如果使用多播,则可以简单的方法实现。
假如原来的网络程序仅实现了数据存储功能,需要增加解码处理功能,我们不需要修改原来的程序,增加解码模块即可:
1.新建一个DataModule, 放上一个TTcpClient,设置要连接的服务器端口地址
unit Unit2; interface uses
System.SysUtils, System.Classes, Web.Win.Sockets, utObjEventAgent; type
TDataModule2 = class(TDataModule)
TcpClient1: TTcpClient;
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
FLink:TEventAgent;
end; var
DataModule2: TDataModule2; implementation {%CLASSGROUP 'Vcl.Controls.TControl'} {$R *.dfm} procedure TDataModule2.DataModuleCreate(Sender: TObject);
begin
FLink:=TEventAgent.Create(TcpClient1);
TcpClient1.Active:=True;
end;
procedure TDataModule2.DataModuleDestroy(Sender: TObject);
begin
FLink.Free;
end;
end.
2.接着,只需在不同的模块去接收你的数据,例如数据存储模块:
unit Unit3; interface uses utObjEventAgent, Unit2, Classes, Web.Win.Sockets; type
TPersistModule=class
protected
FStream:TFileStream;
private
procedure OnDataReceive(Sender: TObject; Buf: PAnsiChar; var DataLen: Integer);
public
constructor Create;
destructor Destroy;override;
end;
implementation { TPersistModule } constructor TPersistModule.Create;
var
V:TSocketDataEvent;
begin
inherited Create;
FStream:=TFileStream.Create('C:\test.dat', fmCreate);
V:= Self.OnDataReceive;
DataModule2.FLink.AddEventNotifier('OnReceive', TMethod(V));
end; destructor TPersistModule.Destroy;
var
V:TSocketDataEvent;
begin
V:= Self.OnDataReceive;
DataModule2.FLink.RemoveEventNotifier('OnReceive', TMethod(V));
FStream.Free;
inherited;
end; procedure TPersistModule.OnDataReceive(Sender: TObject; Buf: PAnsiChar;
var DataLen: Integer);
begin
FStream.Write(Buf^, DataLen);
end; end.
3.数据解码模块
unit Unit4; interface uses utObjEventAgent, Unit2, Classes, Web.Win.Sockets, utDecoder; type
TDecodeModule=class
protected
FDecoder:TDecoder;
private
procedure OnData(Sender: TObject; Buf: PAnsiChar; var DataLen: Integer);
public
constructor Create;
destructor Destroy;override;
end;
implementation { TDecodeModule } constructor TDecodeModule.Create;
var
V:TSocketDataEvent;
begin
inherited Create;
FDecoder:=TDecoder.Create
V:= Self.OnData;
DataModule2.FLink.AddEventNotifier('OnReceive', TMethod(V));
end; destructor TDecodeModule.Destroy;
var
V:TSocketDataEvent;
begin
V:= Self.OnData;
DataModule2.FLink.RemoveEventNotifier('OnReceive', TMethod(V));
Fdecoder.Free; inherited;
end; procedure TDecodeModule.OnData(Sender: TObject; Buf: PAnsiChar;
var DataLen: Integer);
begin
FDecoder.Decode(Pointer(Buf), DataLen);
end; end.
再举例2:
借用 “Delphi 实现事件侦听与触发”的例子:
const
evtDataChanged = 'evtDataChanged'; //数据处理类, 用于提供数据
TOnData=procedure( Name, City, CellPhone:String; Age: Integer ) of Object;
TNwDataClass = class( TObject)
private
FOnData:TOnData;
public
Link:TEventAgent;
constructor Create;
destructor Destroy;override;
procedure AddData( Name, City, CellPhone:String; Age: Integer );
property OnData:TOnData read FOnData write FOnData;
end; //界面显示类
TNwInterface = class( TForm )
procedure FormCreate( Sender: TObject );
procedure FormDestroy( Sender: TObject );
protected
procedure OnEvent( Name, City, CellPhone:String; Age: Integer );
procedure OnEvent2( Name, City, CellPhone:String; Age: Integer );
public
procedure AddDataToList( Name, City, CellPhone:String; Age: Integer);
procedure AddDataToFile( Name, City, CellPhone:String; Age: Integer );
end; // TNwDataClass 应该有一个全局的实例, 用于提供数据. 在下面的代码中, 就以
// instanceDataClass 为这个实例
implementation { TNwDataClass }
constructor TNwDataClass.Create;
begin
inherited Create;
Link:=TEventAgent.Create(Self);
end;
destructor TNwDataClass.Destroy;
begin
Link.Free;
inherited;
end;
procedure TNwDataClass.AddData( Name, City, CellPhone:String; Age: Integer );
begin
//数据处理代码,忽视Link的存在
if Assigned(FOnData) then FOnData(Name, City, CellPhone, Age);
end; { TNwInterface }
procedure TNwInterface.FormCreate( Sender: TObject );
var V:TOnData;
begin
V:= Self.OnEvent;
instanceDataClass.Link.AddEventNotifier('OnData', TMethod(V));
V:= Self.OnEvent2;
instanceDataClass.Link.AddEventNotifier('OnData', TMethod(V));
end; procedure TNwInterface.FormDestroy( Sender: TObject );
var V:TOnData;
begin
V:= Self.OnEvent;
instanceDataClass.Link.RemoveEventNotifier('OnData', TMethod(V));
V:= Self.OnEvent2;
instanceDataClass.Link.RemoveEventNotifier('OnData', TMethod(V));
end; procedure TNwInterface.OnEvent( Name, City, CellPhone:String; Age: Integer );
begin
AddDataToList( Name, City, CellPhone, Age);
end; procedure TNwInterface.OnEvent2( Name, City, CellPhone:String; Age: Integer );
begin
AddDataToFile( Name, City, CellPhone, Age);
end; procedure TNwInterface.AddDataToList( Name, City, CellPhone:String; Age: Integer );
begin
//用于处理显示数据的代码.
end; procedure TNwInterface.AddDataToFile( Name, City, CellPhone:String; Age: Integer );
begin
//用于保存数据的代码.
end;
利用RTTI实现Delphi的多播事件代理研究的更多相关文章
- Atitit事件代理机制原理 基于css class的事件代理
Atitit事件代理机制原理 基于css class的事件代理 1.1. 在javasript中delegate这个词经常出现,看字面的意思,代理.委托1 1.2. 事件代理1 1.3. 代理标准化规 ...
- js中的事件委托或是事件代理详解
起因: 1.这是前端面试的经典题型,要去找工作的小伙伴看看还是有帮助的: 2.其实我一直都没弄明白,写这个一是为了备忘,二是给其他的知其然不知其所以然的小伙伴们以参考: 概述: 那什么叫事件委托呢?它 ...
- JS 事件代理
事件处理器:onclick.onmouseover.... 在传统的事件处理中,你需要为每一个元素添加或者是删除事件处理器.然而,事件处理器将有可能导致内存泄露或者是性能下降——你用得越多这种风险就越 ...
- javascript事件代理(委托)
之前有接触过事件代理,但是印象并不深刻.这次记下来加强印象. 用个大家比较常见的代码举例子: html dom结构: <ul id="ul1"> <li>0 ...
- JS中事件代理与委托
在javasript中delegate这个词经常出现,看字面的意思,代理.委托.那么它究竟在什么样的情况下使用?它的原理又是什么?在各种框架中,也经常能看到delegate相关的接口.这些接口又有什么 ...
- 关于JavaScript中的事件代理
今天面试某家公司Web前端开发岗位,前面的问题回答的都还算凑活,并且又问了一下昨天面试时做的一道数组去重问题的解题思路(关于数组去重问题,可以观赏我前几天写的:http://www.cnblogs.c ...
- JavaScript中事件委托(事件代理)详解
在JavaScript的事件中,存在事件委托(事件代理),那么什么是事件委托呢? 事件委托在生活中的例子: 有三个同事预计会在周一收到快递.为签收快递,有两种办法:一是三个人在公司门口等快递:二是委托 ...
- [转] js中的事件委托或是事件代理详解
起因: 1.这是前端面试的经典题型,要去找工作的小伙伴看看还是有帮助的: 2.其实我一直都没弄明白,写这个一是为了备忘,二是给其他的知其然不知其所以然的小伙伴们以参考: 概述: 那什么叫事件委托呢?它 ...
- 【转载】浅谈事件冒泡与事件捕获 - javascript 事件代理
原文:https://segmentfault.com/a/1190000000749838 事件冒泡与事件捕获 事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发 ...
随机推荐
- 2.bootstrap安装
1.下载 您可以从 http://getbootstrap.com/ 上下载 Bootstrap 的最新版本(或者 http://www.bootcss.com/ 中文网) Download Boo ...
- django从1.7升级到1.9后 提示:RemovedInDjango110Warning
Django项目,把django从1.7升级到1.9后,大量报错.需要做如下修改. 1,修改urls.py: 在django1.9里,urls的配置不再支持字符串型的路由.需要先import,然后直接 ...
- hibernate对象的三种状态及转换
一.hibernate对象三种状态 Transient(瞬时状态):没有session管理,同时数据库没有对应记录 举例:new 出来的对象还没有被session管理,此时该对象处于Transient ...
- 激活老电脑的第二春:内存盘为Chrome浏览器做缓存
AMD Radeon RAMDisk 4.2.1 正式版 下载地址:http://dl.pconline.com.cn/html_2/1/73/id=7204&pn=0.html 适用于:wi ...
- HTML5 网页 漂浮窗广告 JavaScript逻辑 - demo
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- unittest:2 执行多条用例,仅执行一次setUp和tearDown
对象方法setUp()和tearDown() 每个用例执行前后都会被调用.但是有另外一种场景:setUp之后执行完所有用例,最后调用一次tearDown.比如打开网页,多条用例分别验证网页上的元素正确 ...
- struts配置中的常量定义
一.常量可以在struts.xml或struts.properties中配置,建议在struts.xml中配置,两种配置方式如下: (1)在struts.xml文件中配置常量 <struts&g ...
- mongod入门实战
mongod-入门 摘要: 本篇文档,带你快速启动一个mongod,到搭建主从+复制集模式的入门. 内容包括:单实例安装,复制集构建,分片构建,分片及复制集整合. 软件相关信息介绍 MongoDB 是 ...
- ZooKeeper学习之路 (七)ZooKeeper设计特点及典型应用场景
ZooKeeper 特点/设计目的 ZooKeeper 作为一个集群提供数据一致的协调服务,自然,最好的方式就是在整个集群中的 各服务节点进行数据的复制和同步. 数据复制的好处 1.容错:一个节点出错 ...
- 人人开源之renren-security
renren-security,从这周开始我将要对其比较详细的研究,之前的研究只不过是比较浅的.正如在这篇文章读读<编写高质量代码:改善Java程序的151条建议>中说过不要重复造轮子. ...