12.3.1 RTL 中的类引用**
System
单元和其他核心 RTL 单元声明了许多类引用,包括以下几种:
TClass = class of TObject;
ExceptClass = class of Exception;
TComponentClass = class of TComponent;
TControlClass = class of TControl;
TFormClass = class of TForm;
特别是 TClass
类引用类型可用于存储对 Object Pascal 中编写的任何类的引用,因为每个类最终都是从 TObject
派生的。相反,TFormClass
引用则在基于 FireMonkey 或 VCL 的默认 Object Pascal 项目的源代码中使用。实际上,这两个库的 Application 对象的 CreateForm
方法作为参数需要要创建的窗体的类:
Application.CreateForm(TMyForm1, MyForm);
第一个参数是一个类引用,第二个是将接收对创建的对象实例的引用的变量。
12.3.2 使用类引用创建组件
在 Object Pascal 中,类引用有什么实际用途呢?能够在运行时使用数据类型是运行环境的基本能力。当你从Component Palette(组件面板)中选择一个新组件添加到窗体中时,你就选择了一种数据类型并创建了该数据类型的对象。(实际上,这就是开发环境在幕后为你做的事情)。
为了让你更好地了解类引用是如何工作的,我创建了一个名为 ClassRef 的示例。这个示例显示的窗体非常简单。窗体的上半部分的面板中放置了三个单选按钮。当你选择其中一个单选按钮并单击窗体时,就可以创建按钮标签所示的三种类型的新组件:单选按钮、普通按钮和编辑框。要使程序正常运行,需要更改这三个组件的名称,因为组件名称必须是唯一的。窗体还必须有一个类引用字段:
private
FControlType: TControlClass;
FControlNo: Integer;
每当用户点击三个单选按钮中的一个,第一个字段就会存储一个新的数据类型,从而改变其状态。以下是三种方法之一:
procedure TForm1.RadioButtonRadioChange(Sender: TObject);
begin
FControlType := TRadioButton;
end;
其他两个单选按钮的 OnChange 事件处理器与此类似,都将 TEdit 或 TButton 的值赋值给 FControlType 字段。类似的赋值也出现在窗体的 OnCreate 事件处理器中,用作初始化方法。当用户点击覆盖窗体大部分表面的 TLayout 控件时,代码中有趣的部分就会被执行。我选择了窗体的 OnMouseDown 事件来保持鼠标点击的位置:
// 另外两个单选按钮有类似于这个的 OnChange 事件处理程序,将值 TEdit 或 TButton 分配给 FControlType 字段。一个类似的赋值也出现在表单的 OnCreate 事件处理程序中,用作初始化方法。代码的有趣部分是在用户点击覆盖整个表单表面的 TLayout 控件时执行的。我选择使用表单的 OnMouseDown 事件来保存鼠标点击的位置:
procedure TForm1.Layout1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
var
NewCtrl: TControl;
NewName: string;
begin
// 创建控件
NewCtrl := FControlType.Create(Self);
// 暂时隐藏它,以避免闪烁
NewCtrl.Visible := False;
// 设置父级和位置
NewCtrl.Parent := Layout1;
NewCtrl.Position.X := X;
NewCtrl.Position.Y := Y;
// 计算唯一的名称(和文本)
Inc(FControlNo);
NewName := FControlType.ClassName + FControlNo.ToString;
Delete(NewName, 1, 1);
NewCtrl.Name := NewName;
// 现在显示它
NewCtrl.Visible := True;
end;
该方法的第一行代码是关键。它将创建一个存储在 FControlType 字段中的类数据类型的新对象。我们只需将 Create 构造函数应用于类引用即可实现这一目的。
现在,您可以设置父属性的值、设置新组件的位置、为其命名(名字会自动用作文本)并使其可见。
请特别注意用来创建名称的代码:为了模仿 Object Pascal 的默认命名约定,我通过表达式 FControlType.ClassName 获取了类的名称,这是TObject 类的类方法。然后,我在名称末尾添加了一个数字,并删除了字符串的首字母。
对于第一个单选按钮,假设 FControlNo 是 1,那么 FControlType 就是 TRadioButton;因此,FControlType.ClassName 返回字符串 “TRadioButton”,FControlNo.AsString 返回 “1”,我们在复制字符串减去第一个字符(即减去字符串开头的 “T”)之前将 "1 "追加到字符串末尾,从而得到新实例使用的最终字符串 “RadioButton1”。看起来熟悉吗?
图 12.1:在 Window 下运行 ClassRef 应用程序的输出示例
图 12.1 是该程序的输出示例。请注意,该程序的命名与集成开发环境的命名并不完全相同,集成开发环境会为每种类型的控件使用单独的计数器。
该程序对所有组件都使用一个计数器。因此,在运行 ClassRef 应用程序时,如果先放置一个按钮,然后是一个单选按钮,接着是两个编辑按钮,然后是另一个按钮,它们的名称将是 Button1、RadioButton2、Edit3、Edit4、Button5,如图所示。
请注意,与设计器不同,创建的编辑控件名称不可见。
顺便提一下,一旦创建了通用组件,就可以使用反射以非常动态的方式访问其属性,这在第 16 章中有详细介绍。在同一章中,我们将看到除了类引用外,还有其他方法来引用类型和类信息。