12.4.2 类助手和继承
类助手的最大限制是,在任何时候,每个类只能有一个类助手。如果编译器遇到两个类助手,第二个类助手将取代第一个。没有办法将类助手串联起来,也就是说,没有办法用一个类助手再进一步扩展一个已经被其他类助手扩展过的类。
这个问题的部分解决方案来自于这样一个事实:你可以为一个类引入一个类助手,并为该类的子类添加一个进一步的类助手;但你不能直接从另一个类助手继承一个类助手。我并不鼓励使用复杂的类助手结构,因为它们会让你的代码变得错综复杂。
当你要添加到一个库或系统定义的数据结构时,类助手才真正有意义。TGUID 记录就是一个例子,它是一种 Windows 数据结构,使用一个类助手增加了一些通用的功能,从而可以在跨平台的 Object Pascal 中使用:
type
TGUIDHelper = record helper for TGUID
class function Create(const B: TBytes): TGUID; overload; static;
class function Create(const S: string): TGUID; overload; static;
class function NewGuid: TGUID; static;
function ToByteArray: TBytes;
function ToString: string;
end;
你可能已经注意到 TGUIDHelper
是一个记录助手而不是类助手。是的,记录可以像类一样拥有助手。
12.4.3 使用类助手添加控件枚举
Delphi 库中的任何组件都会自动定义一个枚举器,你可以使用它来处理每个拥有的组件,或子组件。例如,在窗体方法中,你可以通过编写以下代码来枚举窗体拥有的组件:
for var AComp in Self do
... // 使用 AComp
另一个常用的操作是遍历子控件,其中只包括以窗体为直接父控件的可视化组件(不包括非可视化组件,如 TMainMenu
,也不包括由子控件托管的控件,如面板上的按钮)。我们可以使用一种技术来编写更简单的代码循环浏览子控件:为TWinControl
类编写一个类助手,增加新的枚举功能:
type
TControlsEnumHelper = class helper for TWinControl
type
TControlsEnum = class
private
NPos: Integer;
FControl: TWinControl;
public
constructor Create(AControl: TWinControl);
function MoveNext: Boolean;
function GetCurrent: TControl;
property Current: TControl read GetCurrent;
end;
public
function GetEnumerator: TControlsEnum;
end;
注解 该类助手是针对
TWinControl
而不是TControl
,原因是只有具有窗口句柄的控件才能成为其他控件的父控件。这基本上排除了图形控件。
这里是类助手的完整代码,包括其唯一的方法和嵌套类 TControlsEnum
的方法:
code{ TControlsEnumHelper }
function TControlsEnumHelper.GetEnumerator: TControlsEnum;
begin
Result := TControlsEnum.Create(Self);
end;
{ TControlsEnumHelper.TControlsEnum }
constructor TControlsEnumHelper.TControlsEnum.Create(AControl: TWinControl);
begin
FControl := AControl;
NPos := -1;
end;
function TControlsEnumHelper.TControlsEnum.GetCurrent: TControl;
begin
Result := FControl.Controls[NPos];
end;
function TControlsEnumHelper.TControlsEnum.MoveNext: Boolean;
begin
Inc(NPos);
Result := NPos < FControl.ControlCount;
end;
图 12.2: 在 Delphi IDE 中用于设计时测试控件枚举助手的窗体
现在,我们创建了一个如图 12.2 所示的窗体,从而可以测试在各种场景中的枚举。第一种情况是我们为其专门编写代码的,即枚举窗体中的子控件:
procedure TControlEnumForm.BtnFormChildClick(Sender: TObject);
begin
Memo1.Lines.Add('Form Child');
for var ACtrl in Self do
Memo1.Lines.Add(ACtrl.Name);
Memo1.Lines.Add('');
end;
这是Memo
控件的操作输出,列出了窗体的子控件,但不包括面板中的其他组件或控件:
Form Child
Memo1
BtnFormChild
Edit1
CheckBox1
RadioButton1
Panel1
BtnFormComps
BtnButtonChild
如果我们枚举所有组件,就可以看到完整的列表。然而,我们在本节开始时编写的代码存在问题,因为我们已经使用新版本的GetNumerator
(在类助手中)的方法覆盖了原来的方法,因此我们不能直接访问基类 TComponent 中的枚举器。该类助手是为 TWinControl
定义的,因此我们可以使用一个技巧:我们将我们的对象强制转换为 TComponent
,代码将调用标准的、预定义的枚举器:
procedure TControlEnumForm.BtnFormCompsClick(Sender: TObject);
begin
Memo1.Lines.Add('Form Components');
for var AComp in TComponent(Self) do
Memo1.Lines.Add(AComp.Name);
Memo1.Lines.Add('');
end;
这是输出的结果,列出了比前一个列表更多的组件:
Form Components
Memo1
BtnFormChild
Edit1
CheckBox1
RadioButton1
Panel1
BtnPanelChild
ComboBox1
BtnFormComps
BtnButtonChild
MainMenu1
在 ControlsEnum
示例中,我还添加了用于枚举面板子控件和一个按钮子控件的代码 的子控件的代码(主要是为了测试枚举器在列表为空时能否正常工作 在列表为空时能否正常工作)。