我正在尝试编写一个从obj文件加载3D对象的组件。
我正在为GetActiveProject.FileName
使用 ToolsAPI 库。我将 designide.dcp 添加到bpl中的Requiers部分。我在TViewPort3D上放置了这个对象的实例时注册了我的对象和设计,我把它放在一切正常之前我可以看到obj文件中的对象被加载到场景中,但是当我尝试编译项目时收到错误消息,指出找不到ToolsAPI.dcu。
我用来加载obj文件的过程是(Model of Model变量是TModel3D):
procedure TMyObject.LoadModel(fileName: string);
begin
if(csDesigning in ComponentState)then
Model.LoadFromFile(IncludeTrailingPathDelimiter(ExtractFilePath(GetActiveProject.FileName))+'Obj\'+filename)
else
Model.LoadFromFile(IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)))+'Obj\'+filename);
end;
此过程在构造函数中使用如下(TMyObject继承自TDummy):
constructor TMyObject.Create(AOwner:TComponent)
begin
inherited;
Model:=TModel3D.Create(Self);
Model.Parent:=Self;
LoadModel('Object1.obj');
end;
当组件的主机项目即将要编译时,是否存在阻止使用ToolsAPI库的问题?
我只是在思考类似指令的内容。
{$IFDEF DESIGNTIME}
uses ToolsAPI;
{$ENDIF}
但是有可能做这样的事吗?
答案 0 :(得分:2)
听起来好像是在尝试将设计时代码编译成运行时项目。运行时包或可执行文件。这是不允许的。您根本无法将任何ToolsAPI单元编译到非设计时包的项目中。
您当然可以使用条件编译来排除ToolsAPI单元,但您必须定义自己的条件定义。没有内置的条件可以满足您的需求。
但使用条件编译可能不是最好的解决方案。通常,您会将使用Tools API的代码分成不同的单元,并且只在设计时项目中包含这些单元。
因此,组件的代码将被拆分为两个单元。第一个单位uMyComp.pas
表示包含大部分代码。该单元声明组件并提供其实现。 uMyComp.pas
中没有任何内容引用ToolsAPI。第二个单元uMyCompReg.pas
表示执行组件注册以及需要ToolsAPI的任何其他任务。这些单位之间存在依赖关系uMyCompReg.pas
使用uMyComp.pas
。然后,您的设计时包将包含两个单元,而非设计时的任何其他项目将仅包含uMyComp.pas
。
使用条件可以达到相同的效果。设计时项目将定义一个条件来表明这是设计时间。因此,项目设置可能包含名为DESIGNTIME
的条件的定义。然后,组件的所有代码都将驻留在名为uMyComp.pas
的单元中。与设计时间相关的任何代码都以DESIGNTIME
为条件。包含uMyComp.pas
的任何其他项目都不会定义DESIGNTIME
,因此将省略仅设计时代码。
虽然这是可能的,但在我看来,这不是解决问题的最佳方法。实际上,如果你查看组件开发的大量开源示例,如果您发现任何处理设计时代码与使用条件的运行时代码分离的话,我会感到惊讶。
您如何将ToolsAPI代码分成设计时单元?这是问题方法:
procedure TMyObject.LoadModel(fileName: string);
begin
if csDesigning in ComponentState then
Model.LoadFromFile(IncludeTrailingPathDelimiter(
ExtractFilePath(GetActiveProject.FileName))+'Obj\'+filename)
else
Model.LoadFromFile(IncludeTrailingPathDelimiter(
ExtractFilePath(ParamStr(0)))+'Obj\'+filename);
end;
首先,让我们来看看这段代码的共性。首先要考虑的是LoadFromFile
的调用外部是相同的。只有在中间,目录的选择,是否有变化。所以让我们这样写:
procedure TMyObject.LoadModel(fileName: string);
var
ModelDir: string;
begin
if csDesigning in ComponentState then
ModelDir := ExtractFilePath(GetActiveProject.FileName)
else
ModelDir := ExtractFilePath(ParamStr(0));
Model.LoadFromFile(IncludeTrailingPathDelimiter(ModelDir)+'Obj\'+filename);
end;
问题在于如何将GetActiveProject.FileName
移动到设计时代码中。您需要使用依赖注入(DI)来执行此操作。允许其他一方提供逻辑。你需要让TMyObject
不知道这个特殊的细节。你可以使用一个DI框架,但这对于这一项任务来说可能只是一个小重量级。因此,让我们声明一个包含函数指针的类变量:
type
TMyObject = class(...)
...
public
class var GetModelDir: TFunc<string>;
end;
此功能点允许类外部的其他方指定模型目录的位置。现在LoadModel
成为:
procedure TMyObject.LoadModel(fileName: string);
var
ModelDir: string;
begin
if Assigned(GetModelDir) then
ModelDir := GetModelDir()
else
ModelDir := ExtractFilePath(ParamStr(0));
Model.LoadFromFile(IncludeTrailingPathDelimiter(ModelDir)+'Obj\'+filename);
end;
此时,您的代码现在可以在设计时包之外使用。下一步是添加代码以在设计时指定GetModelDir
。此代码仅在设计时间单元中注册组件。代码的明显位置在该单元的初始化部分。它看起来像这样:
initialization
TMyObject.GetModelDir :=
function: string
begin
Result := GetActiveProject.FileName;
end;
我在这里使用过匿名方法,但您可以使用对象方法或普通旧功能类型,具体取决于您的Delphi版本。
答案 1 :(得分:2)
是的,但最好不要使用条件定义,因为这会产生更多的复杂性和限制,而不是它的价值。
使用上述包结构,使用新组件的应用程序应仅依赖于运行时单元。