C#中,事件是以委托的形式出现的,我们可以使用+=操作符来实现一个组件中同一事件的串联。但是,在Delphi中,事件是以procedure of Object的形式出现的,没有+=操作符的支持,也就是说,一个组件的事件只能指向一个函数地址(C#中的委托使用了指针列表来保存委托,因此可以实现多个事件的挂接)。那么,有没有一个变通的方法来间接实现事件的随意“注入”(即不影响原有事件,把自己的多个事件插入到事件执行队列,或者一次插入到多个事件中)呢?答案是肯定的。好,让我们一起来。
所谓事件,实质是一个实现定义了形式的函数指针,那么既然是指针,就可以用地址来表示。
比如,有一个按钮,我们想在它的OnClick事件注入我们的代码,那么可以这样:
//这里是全局变量
var
temp: TNotifyEvent;
//下面是实现
begin
temp := Button.OnClick; //把原有事件保留到临时的变量
Button.OnClick := myClick; //用我们的事件替换之
end;
//在myClick事件函数中写入
if Assigned(temp) then
temp(Sender);
这样,我们成功的使用自己的事件“注入”了原有事件。
这是在组件或者事件个数明确的情况下实现的,那么组件的个数不明确的话我们该怎么办呢?其实,我们完全可以使用指针数组/列表来实现事件的“注入”,只是其中使用了一个小小的转换来保证类型的一致。
比如,有一个PageControl控件,我们需要注入到每一个TabSheet的OnShow中,而对于TabSheet的个数我们不明确,那么可以这样:
//定义两个列表
var
OnTabSheetShowCodeList: TList; //用于保存TMethod.Code的内容
OnTabSheetShowDataList: TList; //用于保存TMethod.Data的内容
//在注入代码实现中
var
os: TMethod;
begin
for i := 0 to PageControl.PageCount -1 do
begin
os := TMethod(PageControl.Pages[i].OnShow); //获取指定TabSheet的OnShow事件,并将其转换成TMethod类型,这里是关键
OnTabSheetShowCodeList.Add(os.Code); //保存该函数指针的地址
OnTabSheetShowDataList.Add(os.Data); //保存该函数指针的额外数据
PageControl.Pages[i].OnShow := myOnShow; //使用我们自己的事件函数来替换之
end;
end;
//那么在myOnShow中
var
onShow: TNotifyEvent;
begin
if (Sender <>nil) and(Assigned((Sender as TTabSheet).PageControl)) then
begin
TMethod(onShow).Code := (FOnTabSheetShowCodeList.Items[(Sender as TTabSheet).pageIndex]); //恢复函数指针的地址
TMethod(onShow).Data := (FOnTabSheetShowDataList.Items[(Sender as TTabSheet).pageIndex]); //恢复函数指针的额外数据
if (Assigned(onShow)) then
begin
onShow(Sender); //调用原始事件
end;
end;
end;
上面代码最不容易理解的是 TMethod(onShow).Data 这个变量,网上很多例子都对它赋予了Self,但是在本例中,赋予Self反而会在调用原始事件时出错,原因很简单,TMethod.Data是对象实例(Instance)的首地址,TMethod的结构定义方式就是目前O-O语言实现的原理。Code和Data分离,Code表示类的首地址,Data表示类的实例的首地址。方法表示对Code的偏移量,字段(Field)表示对Data的偏移量。因此,如果赋予其Self的话,在调用原始事件时就会出现原始调用者指针丢失,造成内存访问异常。
[e01]