事件的基本用法
事件的基本用法
我在对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 方法
说明某个类的方法指针域可以具有指向其他类的方法的值
属于方法指针类型,用于处理事件。