在Delphi的开发中,经常会遇到创建很多相同的类或者相似的类的实例,期望通过索引动态的定位这些实例。
例如: delphi 中,有几个类似窗体,例如TTest_Form1,TTest_Form2,TTest_Form3,TTest_Form4等,期望通过索引能够找到对应的类,并创建实例,例如知道索引是3,那么就会用TTest_Form3.create创建对象实例。
一、使用类引用数组(Class of TForm 或者具体类的类引用)
-
步骤:
- 定义一个类型别名,表示窗体类的类引用。对于具体窗体可以使用通用的
TTest_Form的子类型,或直接使用class of TForm。 - 将各个窗体类的类型放入一个数组,数组下标与你希望的“索引”对应。
- 根据索引取出类引用,然后调用
Create构造对象(通常需要传入所有权所有者,如Owner)。 - 可选:统一初始化、显示、释放等流程。
- 定义一个类型别名,表示窗体类的类引用。对于具体窗体可以使用通用的
-
代码示例(假设窗体都继承自
TForm,且构造参数为Owner: TComponent):
type
// 窗体类的通用类引用类型
TFormClass = class of TForm;
// 如果你确实只有 TTest_Form1..TTest_Form4 这几个
// 也可以直接用 TFormClass,数组中存放它们的类引用
// 在某个单位的实现部分,或者一个工厂单元里
const
// 注意下标从 0 开始,或者按你需要的起始下标调整
// 将 1: TTest_Form1, 2: TTest_Form2, ...
var
FormClasses: array of TFormClass;
idx: Integer;
NewForm: TForm;
begin
// 初始化类引用数组(只需在程序启动阶段或需要时设置一次)
SetLength(FormClasses, 4);
FormClasses[0] := TTest_Form1;
FormClasses[1] := TTest_Form2;
FormClasses[2] := TTest_Form3;
FormClasses[3] := TTest_Form4;
// 假设索引为 3,想创建 TTest_Form3 的实例
idx := 3 - 1; // 根据你使用的索引起始,若你希望索引直接对应数组下标,则不需要 -1
if (idx >= 0) and (idx < Length(FormClasses)) and Assigned(FormClasses[idx]) then
begin
NewForm := FormClasses[idx].Create(Application); // 根据你的Owner 调整
try
NewForm.Show; // 或者 ShowModal、BringToFront 等
except
NewForm.Free;
raise;
end;
end
else
raise Exception.Create('Invalid form index');
end;
- 注意事项:
- 管理
Owner:通常传Application或特定父窗体。若直接创建非可视窗体,确保正确管理生命周期。 - 异常处理:创建失败时要释放资源,避免内存泄漏。
- 界面释放:根据窗体的用途,选择
Free、FreeAndNil,以及是否需要在显示后再释放(如模态对话框在返回后自行释放,需自行管理)。
- 管理
二、使用工厂函数表(更灵活、便于扩展)
- 定义一个工厂函数类型,返回
TForm,并把各窗体的工厂函数放入数组。 - 优点:若窗体需要自定义初始化参数,可以在工厂函数中完成。
type
TFormFactory = function: TForm of object;
function CreateTestForm1: TForm;
begin
Result := TTest_Form1.Create(Application);
// 额外初始化
end;
function CreateTestForm2: TForm;
begin
Result := TTest_Form2.Create(Application);
end;
// 依此类推
var
FormFactories: array of TFormFactory;
idx: Integer;
FormObj: TForm;
begin
SetLength(FormFactories, 4);
FormFactories[0] := @CreateTestForm1;
FormFactories[1] := @CreateTestForm2;
// ...
idx := 3 - 1;
if (idx >= 0) and (idx < Length(FormFactories)) and Assigned(FormFactories[idx]) then
begin
FormObj := FormFactories[idx]();
try
FormObj.Show;
except
FormObj.Free;
raise;
end;
end
else
raise Exception.Create('Invalid form index');
end;
三、使用 RTTI 动态创建
- 如果你的窗体命名规则严格(如 TTest_Form1, TTest_Form2,...),也可以利用 RTTI 根据名称动态创建。
- 但这会涉及到运行时类型信息和字符串拼接,容易出错且效率低。
示例(简化,实际使用需引入 RTTI 单元):
uses
Rtti, TypInfo;
function CreateFormByIndex(Index: Integer; Owner: TComponent): TForm;
var
Ctx: TRttiContext;
Typ: TRttiType;
TypeName: string;
FormInstance: TValue;
begin
// 根据索引映射到类名,如 3 -> 'TTest_Form3'
TypeName := Format('TTest_Form%d', [Index]);
Typ := Ctx.FindType(TypeName);
if Typ = nil then
raise Exception.Create('Unknown form type: ' + TypeName);
if not (Typ is TRttiInstanceType) then
raise Exception.Create(TypeName + ' is not a class type');
// 创建实例:需要知道构造参数,此处假设有无参构造且 Owner 通过参数传入
// 由于 RTTI 创建无参构造较复杂,通常不推荐直接用 RTTI 动态创建窗体
Result := nil; // 更复杂实现略
end;
总结与推荐
- 最稳妥、简单且可维护的方式,是将窗体类引用放入一个数组(方法 A),或用工厂函数表(方法 B),按索引选择创建。这样编译期明确、性能好、容易调试。
- 如果未来窗体数量极大且经常新增,方法 B(工厂函数表)会更易扩展,因为你只需添加一个新的工厂函数并更新数组。
- 在使用时,注意:同一位置的索引应与数组下标或你的业务逻辑严格对应,若要从 1 开始索引,记得做相应的偏移处理。
751

被折叠的 条评论
为什么被折叠?



