是否可以在delphi中使用库只是为了设计时间?

时间:2014-05-21 13:03:04

标签: delphi custom-component

我正在尝试编写一个从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}

但是有可能做这样的事吗?

2 个答案:

答案 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)

是的,但最好不要使用条件定义,因为这会产生更多的复杂性和限制,而不是它的价值。

  • 您需要根据代码的设计时代码或运行时代码将代码分成不同的单元。
    • E.g。对于单个组件,大部分(没有ToolsAPI依赖)进入一个单元。
    • 第二个单元执行组件注册,并可能为组件提供自定义设计时编辑器。
    • 第二个单位使用第一个单位,你有一个没有条件定义的清晰分离。
  • 然后创建2个单独的包:设计时和运行时。
  • 设计时包将依赖于ToolsAPI。
  • 确保所有运行时单元都不使用任何设计时单位。
  • 如果任何设计时单位使用运行时单位(非常可能),则设计时包将要求运行时包。

使用上述包结构,使用新组件的应用程序应仅依赖于运行时单元。