【Delphi】中通过索引动态定位并创建对应窗体类实例
在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, ...
varFormClasses: 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; // 根据你使用的索引起始,若你希望索引直接对应数组下标,则不需要 -1if (idx >= 0) and (idx < Length(FormClasses)) and Assigned(FormClasses[idx]) thenbeginNewForm := FormClasses[idx].Create(Application); // 根据你的Owner 调整tryNewForm.Show; // 或者 ShowModal、BringToFront 等exceptNewForm.Free;raise;end;endelseraise Exception.Create('Invalid form index');
end;
- 注意事项:
- 管理
Owner
:通常传Application
或特定父窗体。若直接创建非可视窗体,确保正确管理生命周期。 - 异常处理:创建失败时要释放资源,避免内存泄漏。
- 界面释放:根据窗体的用途,选择
Free
、FreeAndNil
,以及是否需要在显示后再释放(如模态对话框在返回后自行释放,需自行管理)。
- 管理
二、使用工厂函数表(更灵活、便于扩展)
- 定义一个工厂函数类型,返回
TForm
,并把各窗体的工厂函数放入数组。 - 优点:若窗体需要自定义初始化参数,可以在工厂函数中完成。
typeTFormFactory = function: TForm of object;function CreateTestForm1: TForm;
beginResult := TTest_Form1.Create(Application);// 额外初始化
end;function CreateTestForm2: TForm;
beginResult := TTest_Form2.Create(Application);
end;// 依此类推
varFormFactories: array of TFormFactory;idx: Integer;FormObj: TForm;
beginSetLength(FormFactories, 4);FormFactories[0] := @CreateTestForm1;FormFactories[1] := @CreateTestForm2;// ...idx := 3 - 1;if (idx >= 0) and (idx < Length(FormFactories)) and Assigned(FormFactories[idx]) thenbeginFormObj := FormFactories[idx]();tryFormObj.Show;exceptFormObj.Free;raise;end;endelseraise Exception.Create('Invalid form index');
end;
三、使用 RTTI 动态创建
- 如果你的窗体命名规则严格(如 TTest_Form1, TTest_Form2,...),也可以利用 RTTI 根据名称动态创建。
- 但这会涉及到运行时类型信息和字符串拼接,容易出错且效率低。
示例(简化,实际使用需引入 RTTI 单元):
usesRtti, TypInfo;function CreateFormByIndex(Index: Integer; Owner: TComponent): TForm;
varCtx: TRttiContext;Typ: TRttiType;TypeName: string;FormInstance: TValue;
begin// 根据索引映射到类名,如 3 -> 'TTest_Form3'TypeName := Format('TTest_Form%d', [Index]);Typ := Ctx.FindType(TypeName);if Typ = nil thenraise Exception.Create('Unknown form type: ' + TypeName);if not (Typ is TRttiInstanceType) thenraise Exception.Create(TypeName + ' is not a class type');// 创建实例:需要知道构造参数,此处假设有无参构造且 Owner 通过参数传入// 由于 RTTI 创建无参构造较复杂,通常不推荐直接用 RTTI 动态创建窗体Result := nil; // 更复杂实现略
end;
总结与推荐
- 最稳妥、简单且可维护的方式,是将窗体类引用放入一个数组(方法 A),或用工厂函数表(方法 B),按索引选择创建。这样编译期明确、性能好、容易调试。
- 如果未来窗体数量极大且经常新增,方法 B(工厂函数表)会更易扩展,因为你只需添加一个新的工厂函数并更新数组。
- 在使用时,注意:同一位置的索引应与数组下标或你的业务逻辑严格对应,若要从 1 开始索引,记得做相应的偏移处理。