10.3.2 委托的概念
乍一看,委托这项技术的目标可能并不明确,但它却是Object Pascal 组件技术的基石之一。秘密就在委托这个词上。如果有人创建了一个对象,这个对象有一些方法指针,那么你只需把新的方法赋值给这些指针,就可以自由地改变这个对象的行为。听起来耳熟吗?是的,应该很熟悉。
当你为一个按钮添加 OnClick 事件处理器时,开发环境也正是这样做的。按钮有一个名为 OnClick 的方法指针,你可以直接或间接地将窗体的一个方法赋值给它。当用户点击按钮时,这个方法就会被调用,即使你是在其他类(通常是窗体)中定义了这个方法。
下面列出了 Delphi 库中实际用于定义按钮组件的事件处理程序和窗体的相关方法的代码:
type
TNotifyEvent = procedure(Sender: TObject) of object;
TMyButton = class
OnClick: TNotifyEvent;
end;
TForm1 = class(TForm)
procedure Button1Click(Sender: TObject);
Button1: TMyButton;
end;
var
Form1: TForm1;
现在,在过程中,你能这么编写代码:
MyButton.OnClick := Form1.Button1Click;
这段代码与库代码真正唯一的区别在于,OnClick 是一个属性名称,而它实际所引用的数据称为FOnClick。事实上,显示在对象检查器 "事件 "页面中的事件只不过是一个方法指针属性。举例来说,这意味着你可以在设计时动态修改附加到组件上的事件处理程序,甚至可以在运行时创建一个新组件并为其分配一个事件处理程序。
DynamicEvents 示例展示了这两种情况。窗体中有一个按钮,按钮关联了一个标准的 OnClick 事件处理器。不过,我在窗体中又添加了具有相同签名(相同参数)的public方法:
public
procedure BtnTest2Click(Sender: TObject);
当按钮被按下时,除了显示一个消息外,还把事件处理器切换到第二个,改变了这个点击操作以后的行为。
procedure TForm1.BtnTestClick(Sender: TObject);
begin
ShowMessage('Test message');
BtnTest.OnClick := BtnTest2Click;
end;
procedure TForm1.BtnTest2Click(Sender: TObject);
begin
ShowMessage('Test message, again');
end;
现在,当你第一次点击按钮时,第一个(默认)事件处理程序就会被执行,而接下来的所有点击都会触发第二个事件处理程序。
注意 当您键入代码将方法赋值给事件时,IDE的"代码自动完成 "会向您推荐可用的事件名称,并将其转化为实际的函数调用,在末尾加上括号。这是不正确的。您必须将方法本身赋值给事件,而不是调用它。否则,编译器会尝试赋值方法调用的结果(作为一个过程,它并不存在),从而导致错误。
项目的第二部分演示了一个完全的动态事件关联。当您点击窗体的表面时,会动态创建一个新按钮,该按钮的事件处理程序会显示关联按钮的标题(Sender 对象将指向调用事件处理程序动态创建的按钮):
procedure TForm1.BtnNewClick(Sender: TObject);
begin
ShowMessage('You selected ' + (Sender as TButton).Text);
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
var
AButton: TButton;
begin
AButton := TButton.Create(Self);
AButton.Parent := Self;
AButton.SetBounds(X, Y, 100, 40);
Inc(FCounter);
AButton.Text := 'Button' + IntToStr(FCounter);
AButton.OnClick := BtnNewClick;
end;
由于使用了事件的Sender参数,即使只使用一个事件处理程序,每个动态创建的按钮也会对鼠标点击做出反应,显示与按钮相关的信息。输出示例见图 10.1。