`

事件的基本用法

阅读更多

事件的基本用法
事件的基本用法

我在对RO例子 Variants 跟踪时有了个意外收获,有种茅塞顿开。
提起事件,我也用过,一般都是控件上提供啥事件我就用啥了。
用的最多的就是OnClick事件,至于为啥这样用,我不知道,
稀里湖途地我用了快小半辈子了。

我从下面的代码开始跟踪的:
procedure TVariantsClientMainForm.FormCreate(Sender: TObject);
var
  i: integer;
begin
  for i := 0 to ComponentCount - 1 do
    if (Components[i] is TROMessage) then begin
      rgMessageType.Items.AddObject(TROMessage(Components[i]).Name, Components[i]);
    end;

  rgMessageType.ItemIndex := 1;
  FVariantsService := (RORemoteService as IVariantsService);
end;

在 rgMessageType.Items.AddObject(TROMessage(Components[i]).Name, Components[i]);上
下了一个断点,单步跟踪,当我跟踪到下面代码时:
procedure TStringList.Changed;
begin
  if (FUpdateCount = 0) and Assigned(FOnChange) then
    FOnChange(Self);
end;

当执行FOnChange(Self);时程序无形中地跳到了下面代码的位置:

procedure TCustomRadioGroup.ItemsChange(Sender: TObject);
begin
  if not FReading then
  begin
    if FItemIndex >= FItems.Count then FItemIndex := FItems.Count - 1;
    UpdateButtons;
  end;
end;

呀!这咋这么奇怪呢?TStringList.Changed 一执行程序就会跳到TCustomRadioGroup.ItemsChange这来
这是为啥呢?在这里我郁闷了很久。啥东西的作用始得可以在两个类之间方法的跳来跳去的呢?
于是,我试图往深里跟踪FOnChange(Self);它想到里面看看是咋回事,可是单步不进去呀,一个单之后
就直接跳到TCustomRadioGroup.ItemsChange这里了。我反复地跟踪,很是郁闷,这是为啥呢?

从语法上分析一下FOnChange(Self);
FOnChange: TNotifyEvent;
FOnChange 定义的是一个事件。
TNotifyEvent = procedure(Sender: TObject) of object;

啥意思?定义成这样的事件就可以类之间的方法跳来跳去的吗?
它咋知道从TStringList跳到TCustomRadioGroup呢?它咋不往别的类上跳去呢?
我估计肯定是有啥说法,不可能是瞎乱跳的吧。

我更郁闷了,我垂头又丧气,这是为啥呢?

我突然想起了Items定义的是 TString而当执行rgMessageType.Items.AddObject
AddObject执行的确是TStringlist下的AddObject,因为在TCustomRadioGroup.create中
FItems是这样创建的:FItems := TStringList.Create;

我合计肯定是得有事件赋值了.
我打开TCustomRadioGroup.create下一看还真是这样:
TStringList(FItems).OnChange := ItemsChange;

我明白了事件为啥会跳来跳去的了,原来桥是在这搭起来的.


整体看一下TStringList类

TStringList = class(TStrings)
  private
...
  FOnChange: TNotifyEvent;
..FOnChanging: TNotifyEvent;.
protected
    procedure Changed; virtual;
    procedure Changing; virtual;
...
 public
...
property OnChange: TNotifyEvent read FOnChange write FOnChange;

end;

事件就是这么用,可能就是上面的这样的结构用法.有如下特点:
1、事件看上去就是一个属性,所以在类中添加事件的方法与添加属性是一样的。
   如:property OnChange: TNotifyEvent read FOnChange write FOnChange;

2、添加事件需要定义属性与之相关的域,该域用于存储事件引用的实际方法指针。
   如:FOnChange: TNotifyEvent;
   FOnChange 域为方法指针,在读取OnChange属性时将调用该指针指向的方法;
   而给OnChange属性赋值时,所赋的值(方法地址)将写到FOnChange域中,由它保存
   方法的入口地址
 如,在TCustomRadioGroup.create中有如下语句:
   TStringList(FItems).OnChange := ItemsChange;
  经过这样的赋值之后FOnChange域保存的ItemsChange方法的入口地址。
  也就是说TCustomRadioGroup下的ItemsChange方法的指针。也就是说该方法的首地址。
  所以执行FOnChange(Self);它实际上就是执行ItemsChange方法了。

3、还需要声明一个响应数据域变化的方法
   如:procedure Changed; virtual;
       procedure Changing; virtual;

   看看它是怎么实现的:
procedure TStringList.Changed;
begin
  if (FUpdateCount = 0) and Assigned(FOnChange) then
    FOnChange(Self);
end;
数据域在变化前的事件。
procedure TStringList.Changing;
begin
  if (FUpdateCount = 0) and Assigned(FOnChanging) then
    FOnChanging(Self);
end;

Assigned 方法判断FOnChange域是否已经赋值了,也就是说是否指定了某个方法,
如果没有指定,则其值为nil,否则程序通过FOnChange方法指针域调用具体的方法。

跟据VCL的惯例,有人把这叫夹心面包,皮萨饼,确实在VCL中能看到很多这样的手法。
看看下面的代表,Changing;和Changed;中间夹着一些代码。

procedure TStringList.InsertItem(Index: Integer; const S: string; AObject: TObject);
begin
  Changing;
  if FCount = FCapacity then Grow;
  if Index < FCount then
    System.Move(FList^[Index], FList^[Index + 1],
      (FCount - Index) * SizeOf(TStringItem));
  with FList^[Index] do
  begin
    Pointer(FString) := nil;
    FObject := AObject;
    FString := S;
  end;
  Inc(FCount);
  Changed;
end;

变化前变化后所要触发的事件。


不知道这是不是事通的一般通常的用法,可能是,原来事件是这么个用法。

TStringList(FItems).OnChange := ItemsChange;
结过这么一赋值之后

FOnChanging(Self); 就相当于执行:
相当于调用 TCustomRadioGroup类下的ItemsChange 方法

说明某个类的方法指针域可以具有指向其他类的方法的值
属于方法指针类型,用于处理事件。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics