Delphi中实现事件“注入”

    技术2022-05-11  122

    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的话,在调用原始事件时就会出现原始调用者指针丢失,造成内存访问异常。 


    最新回复(0)