注册自定义表单,以便我可以从多个项目中继承它,而无需将表单复制到对象库文件夹中。

8
我有一个自定义框架需要在多个项目中继承。这个框架包括一些代码和一些组件,它位于磁盘上的某个位置,在它自己的项目目录中。我不想将它复制到对象库文件夹中,这对我来说似乎不正确:我会得到两个副本,一个在我的Mercurial支持的存储库中,另一个在Delphi的对象库中。这绝对不是一个好主意。
我想要的是将我的框架放在一个包中,并让包执行所有必需的操作,使框架为IDE所知,并允许IDE创建给定框架的新兄弟姐妹,而不实际将框架添加到每个单独的项目中。
我迄今为止做了什么,遇到的问题以及我尝试过的解决方案:
1. 我将我的框架添加到一个包中,使用RegisterClass和RegisterNoIcon注册了我的框架。问题:当我进入其他项目并尝试打开派生框架进行编辑时,它会说找不到我的原始框架。 2. 为了解决问题“1”,我想注册我的框架作为自定义模块。因此,我调用了RegisterCustomModule(TMyFrameName, TCustomModule)。问题:从“其他”项目中打开派生框架时,IDE不会在我的原始框架上创建组件,并且IDE会抱怨缺少一个“继承”的组件。 3. 为了解决问题“2”,我认为我应该通过调用InitInheritedComponent(Self, TFrame)来帮助IDE。这有所帮助,当我尝试在“其他”项目中打开框架时,所有内容都被重新创建,我能够按照预期看到框架。问题:当我保存框架时,它会忘记所有关于继承组件的信息,将每个组件视为添加到此特定框架的新组件。如果我查看保存的DFM,所有内容都以“object”开头,而没有像我期望的那样以“inherited”开头。
不幸的是,我卡在了问题“3”上。我尝试深入Classes.pas、ToolsAPI、DesignIntf和DesignEditors,但没有找到任何有用的东西。显然,我希望在DFM中看到的“继承”属性是由TWriter生成的,当它在流式传输TComponent之前分配了“TWriter.Ancestor”属性时,但是没有办法让我设置它,IDE需要为我设置它。而我无法说服IDE为我做这件事。
以下是累积的相关代码部分:
TTestFrame = class(TFrame)
public
  constructor Create(Owner:TComponent);override;
end;

constructor TTestFrame.Create(Owner: TComponent);
begin
  inherited;
  if csDesignInstance in ComponentState then InitInheritedComponent(Self, TFrame);
end;

procedure Register;
begin
  RegisterClass(TTestFrame);
  RegisterNoIcon([TTestFrame]);
  RegisterCustomModule(TTestFrame, TCustomModule);
end;

除了“放弃并将您的东西放入对象存储库中”,还有什么想法吗?

编辑

为什么我需要这样做,以及依赖于实际路径名被写入项目文件的解决方案为什么不起作用:我想支持分支:当一个分支时,可以合理地期望同一项目的多个版本在同一机器的不同目录中“存在”。因此,我不能在同一位置同时拥有同一项目的多个版本。

为确保这一点,我决定让我的项目不依赖于其所在位置,并强制要求我们团队中的每个人在不同的目录中克隆(Mercurial术语)或检出(SVN术语)我们的项目。在我的系统上硬编码路径对我的同事的系统不好:如果我们中任何人错误地将任何路径硬编码到应用程序中,那么不久它就会在我们中的某个人身上发生故障,因此错误得到修复。

这当然是一个问题,因为一些库的表单和框架是其中的一部分(因此它们不在我们项目的目录中),但我们需要从其中继承!为了在处理这些文件时获得IDE支持,我们需要临时将它们添加到项目中,并在完成后不要忘记删除它们。如果我们忘记推送/检入更改,更改将破坏我们同事的构建(因为他们已经在不同位置检出了库)。

为了解决这个问题,我尝试将那些框架和表单添加到设计时包中(使用完整路径加载包,但路径不是项目文件的一部分,因此可以),但不幸的是失败了,这就是我发布这个问题的原因。

3个回答

3
这个问题有几个方面需要考虑:
  • 使用设计时从包中引用框架。
  • 在项目中创建一个继承自包中框架的新框架。
  • 使不同的项目分支可以使用不同版本的包和框架,而不必在dpr或dproj中硬编码路径。

使不同的项目分支可以使用您自己的包的不同版本

  • 在Delphi IDE中使用“工具|选项|环境变量”创建一个环境变量,并将其指向当前分支的框架所在的文件夹。我们称此环境变量为“MyLib”,并将其指向“D:\Whatever\Version1”文件夹。
  • 进入注册表编辑器,将此条目导出到名为“MyLibEnvironmentVariable.reg”的文件中。将此文件放在源代码控制下,并编辑它以删除同一注册表键下还存在的其他环境变量。
  • 导出Delphi安装的已知包的内容。
  • 编辑导出的文件: -- 删除所有不是您自己的包的值。 -- 将您自己包的值更改为例如$(MyLib)\\xxx.bpl,而不是D:\\Whatever\\Version1\xxx.bpl
  • 从已知包键中删除指向您自己包的所有键,并导入您刚刚编辑的文件。这将有效地更改您的包注册以使用MyLib变量,并确保IDE现在也将使用MyLib变量来查找您的包。

在设计时使用包中的框架

  • 创建一个名为“LibraryPackage”的包,将其保存在“D:\Whatever\Version1”文件夹中。
  • 添加一个名为“LibraryFrame”的框架,将其保存在“D:\Whatever\Version1”文件夹中。
  • 在框架上放置一些控件或给它一个丑陋的颜色,以便您可以在视觉上识别它。
  • 在LibraryFrame单元中添加一个Register;过程,并在其实现中放置RegisterComponents('MyFrames', [TLibraryFrame]);
  • 构建包并在IDE中安装它。
  • 从环境选项的库路径中删除对此包的硬编码路径和/或更改它以使用$(MyLib)环境变量。
  • 更改此包的注册表条目以使用MyLib环境变量。
  • 该框架仍然不会在工具箱|框架下可用,但它将在自己的MyFrames工具箱页面中可用,您可以将其包含在任何想要的窗体上。IDE将向表单使用子句添加框架的单元名称。它不应添加任何内容到dpr中,但它可能仍然会向您的dproj添加完全硬编码的路径。为避免这种情况,您还需要对此包执行导出/更改bpl路径以使用$(MyLib)/双击技巧。

从包中继承框架

现在,所有上述内容都已设置好,但是包中的Frame仍然无法在除创建它的库之外的任何其他项目中继承。但是,现在可以很容易地使其可用于继承。只需将 LibraryFrame_fr in 'LibraryFrame_fr.pas' {LibraryFrame}, 添加到您项目的dpr中,当您使用“添加新项|其他”向项目添加内容时,该框架现在将出现在“可继承项”列表中。
不幸的是,当您选择它时,IDE会抱怨:无法打开文件“D:\ Whatever \ SecondFormReusingFrame \ LibraryFrame_fr.pas”。找不到指定的文件。 显然,现在它希望在项目文件夹中找到LibraryFrame,而不是在MyLib文件夹中查找。虽然ctrl-clicking可以带出正确的文件,但IDE也会在表单中标出TLibraryFrame类型。
LibraryFrame_fr in 'LibraryFrame_fr.pas' {LibraryFrame},更改为$(MyLib)\LibraryFrame_fr in 'LibraryFrame_fr.pas' {LibraryFrame},有所帮助,因为IDE现在不再抱怨找不到LibraryFrame_fr.pas。但是,它确实具有这样的效果,即当您保存所有内容时,IDE“友善地”将其更改为相对路径。在我的情况下,是..\ FrameToBeReusedSecond \ LibraryFrame_fr in 'LibraryFrame_fr.pas' {LibraryFrame},,这部分地使目标失败,因为它重新引入了路径名称依赖性。
然而,仅将LibraryFrame_fr in 'LibraryFrame_fr.pas' {LibraryFrame},保留在dpr中也不是一个真正的选择。当您重新加载项目时,IDE会抱怨找不到该框架,即使可以在搜索路径中找到它。
同时,像那样保留它并忽略从此框架派生时的IDE投诉也不是一个真正的选择。IDE不会添加相应的dfm……虽然您可以将dpr中的使用编辑为MyOwnFrame_fr in 'MyOwnFrame_fr.pas' {LibraryFrame1}并手动添加dfm存根,但IDE仍然无法找到LibraryFrame_fr.pas……
总的来说,Cosmin,似乎IDE太过于固执己见,希望和期望的东西太多。我认为您想要的是可实现的,但前提是您愿意并能够使您的库文件夹始终与您的项目处于相同的相对位置。

哇,这应该是一个固定的话题。 <g> > "..library folders always in the same relative location.." - 不知道,也许可以将库文件夹映射到一个驱动器号(subst),以便始终将它们放在相同的绝对位置... - Sertac Akyuz
@Sertac: <g> 谢谢。"映射到驱动器字母" 是的,那可能行得通。将所有包和 dpr 引用指向 L:\Lib,并根据您所在的分支更改 L: 的映射。实质上,您将使用驱动器映射作为 $(myLib) 环境变量的替代品。不过我不确定 IDE 会如何反应,因为已知它对与项目不在同一驱动器(特别是网络驱动器)上的路径非常挑剔。但我认为这绝对值得 Cosmin 探究。 - Marjan Venema
我对“从包中继承框架”非常感兴趣,但你所说的是它实际上并不起作用。如果我只需要“使用框架”,那么我早就完成了,但我并不关心这个。让不同的项目分支使用您自己的软件包的不同版本:你的答案过于复杂了!一旦我能够让一个软件包正常工作,我就会想办法使用相同软件包的不同版本。但到目前为止,我的问题仍未得到解答。感谢你们的帮助。 - Cosmin Prund
@Cosmin:能够在设计时使用框架是继承它的一部分。如果IDE无法正确找到它,您将无法直观地设计下降框架。很抱歉我的努力没有产生直接可用的结果,但事实就是如此。我认为,在我的答案中关于从包中继承框架的信息以及Sertac在评论中提出的建议相结合,可能是您正在寻找的解决方案,但您必须自己进行调查。我已经没有时间了。 - Marjan Venema

1

嗯,将您想要重用的框架添加到您想要重用它的项目中有什么问题吗?

例如:

  • Project1.dpr使用Form1和Frame1,您想要重用Frame1。
  • 在不同的文件夹中启动新的VCL表单项目。
  • 当您单击“工具箱”中“标准”页面上的“框架”时,会显示没有框架。
  • 通过项目管理器将Frame1单元添加到此项目中(以便将其添加到dpr中的使用列表中)。
  • 现在,在“工具箱”中单击“标准”页面上的“框架”将显示可供选择的Frame1。
  • 右键单击项目管理器中的项目,然后选择添加新项|其他并转到“可继承项”,也会显示Frame1作为可继承项。

编辑

如果您不想在dpr中包含硬编码路径,则始终可以使用IDE的环境变量的策略。

转到“工具”|“选项”|“环境变量”。 添加一个名为MYLIB的变量,并将其指向适用于当前分支的文件夹。 添加一个包含文件夹路径的文件并将其添加到源代码控制中。这可以是现在包含MYLIB值的注册表键的导出文件。 将$(MYLIB)添加到项目的库路径中。 将框架添加到项目中。它们现在应该已经包含在dpr中,而不需要路径(因为它们可以在库路径中找到)。 在集成分支时:确保更改包含MYLIB值的源文件。 在切换分支时:激活MYLIB的正确值。如果您将.reg文件添加到源代码控制中:只需双击它即可更改MYLIB注册表键的值。

长话短说,这对我来说是错误的。我正在使用Mercurial进行版本控制,并希望能够“分支”。为了做到这一点,我的项目不应该依赖于安装它们的实际物理路径(显然不能在同一位置同时拥有相同项目的两个版本)。为了确保我的项目真正不依赖于物理路径,我让我们团队中的每个人在不同的文件夹中克隆我们正在工作的项目:我所说的框架并不位于每个人的同一目录中,因此我无法将其添加到项目中! - Cosmin Prund
因此,将其添加到dpr中,而不需要路径,确保源代码可从项目库路径中获取,并在IDE中使用类似于$(BDS)符号的环境变量指向特定分支所需的特定路径。将该环境变量的值放在源代码控制下的文件中,并在更改分支时“激活”它(可以使用.reg文件或手动更改工具|选项|环境变量)。我所有项目中自己的文件夹路径都像$(MVC)<library><folder>。更改MVC的值会更改所使用的源代码... - Marjan Venema
谁给了这个负一分?能否分享一下原因?这是一个可能有效的解决方案,不幸的是我现在没有时间测试它。如果这个方案有效,我会给它一个加一分,并且很有可能我也会接受这个答案。 - Cosmin Prund
嗯,不错的技巧,Marjan... 我会尝试它来解决我遇到的一些路径问题!! - Fabricio Araujo
@Marjan,这些框架需要添加到DPR中,以便IDE允许进行“新建|继承”操作。如果这些文件实际上是给定项目的一部分,我就没有问题将它们添加到DPR中。无论如何,我不认为$(MYLIB)会粘在文件的任何位置:当IDE需要重写DPR时,它只是重写其中每个路径。 - Cosmin Prund
显示剩余4条评论

0

当我们在我上一家公司创建构建过程时,我们想要做完全相同的事情。我们使用Subversion,并拥有一个包含所有共享组件的项目,其中包含一个(Finalbuilder)项目来构建所有共享包。

然后,我将使用类似于Marjan的(MyLib)变量的技术。并从批处理文件中启动Delphi以获取最新的组件集。

最终,我们发现这是不必要的。我们主框架的发布属性很少更改,因此我们只使用一个已安装的软件包集,例如

c:\ BDS \ Components \ D12 \ Bpl(这可能因开发人员而异)

但是,位于

c:\ BDS \ MyProject \ Shared 始终在编译时链接,因为它在构建项目时的相对搜索路径上

c:\ BDS \ MyProjectExperimental 在构建时也会使用正确分支的代码。

我们实际上通过使用CodeSmith(代码生成器)解决了存储库问题,以生成框架,其额外好处是它们将放置在正确的文件夹中,并具有正确的命名约定(以及任何特定于分支的更改)。我们之所以这样做是为了节省时间,但我们(偶然)完全避免了这个问题。

CodeSmith模板在第一帧设置需要花费一些时间,但之后我们很容易就能够适应它来创建其他子类,而不需要过多的脑力(例如dataModules)。

cfEditFrame.dfm.cst

<%@ CodeTemplate Language="C#" TargetLanguage="Delphi" Src="" Inherits="" Debug="False" Description="cfEditFrames.dfm 模板" ResponseEncoding="ASCII" %>  
<%@ Property Name="TypeName" Type="System.String" Default="TypeName" Optional="False" Category="Strings" Description="类型名称。例如:账户;月龄" %>  
继承自 cf<%=TypeName%>EditFrame 的 Tcf<%=TypeName%>EditFrame:  
  宽度 = 425  
  高度 = 63  
end  

cfEditFrame.cst - 我们必须调用的一个脚本,用于生成新的子类化框架

<%@ CodeTemplate Language="C#" TargetLanguage="Delphi" Src="" Inherits="" Debug="False" Description="cfSDMEditComp Template." ResponseEncoding="ASCII" %>  
<%@ Property Name="OutputFolder" Type="System.String" Default="..\\SharedNonInstalled" Optional="False" Category="Strings" Description="" %>  
<%@ Property Name="TypeName" Type="System.String" Default="TypeName" Optional="False" Category="Strings" Description="Type 的名称,例如 Account;AgeMonths。" %>    
<%@ Register Name="EditFramesPasTemplate" Template="cfEditFrames.pas.cst" %>  
<%@ Register Name="EditFramesDfmTemplate" Template="cfEditFrames.dfm.cst" %>  
<%@ Import NameSpace="System.IO" %>  
<script runat="template">  

public override void Render(TextWriter writer)  
{  
  EditFramesPasTemplate cfEditFramesPasTemplate = new EditFramesPasTemplate();  
  this.CopyPropertiesTo(cfEditFramesPasTemplate);  
  cfEditFramesPasTemplate.RenderToFile(String.Format("{0}\\Edit\\cf{1}EditFrames.pas", OutputFolder, TypeName), true);
EditFramesDfmTemplate cfEditFramesDfmTemplate = new EditFramesDfmTemplate();   this.CopyPropertiesTo(cfEditFramesDfmTemplate);   cfEditFramesDfmTemplate.RenderToFile(String.Format("{0}\\Edit\\cf{1}EditFrames.dfm", OutputFolder, TypeName), true); }
</script>

可能会有其他源代码生成器做得同样好,甚至更好。我们有CodeSmith,因此使用它。希望上述文件格式正确。我是一个 SO 新手,希望 html 代码的呈现是正确的。

关于在这些框架上同时拥有属性和组件,你需要做一件奇怪的事情。你需要分两步来完成。首先,在第一个类中添加框架属性,然后在其子类中添加组件。

例如,我们在 cfBaseEditFrames.TcfBaseEditFrame 中添加自定义属性。

然后,我们将其作为 TcfBaseEditFrame 的子类,如 cfEditFrames.TcfEditFrame = class(TcfBaseEditFrame)。

这是我们添加组件的地方(在我们的例子中是 TActionList 和 TImageList)。

在将它们注册为包时,我们添加了:

RegisterCustomModule(TcfBaseEditFrame, TWinControlCustomModule);

然后,我们确保这个包在我们的项目组中,并且没有问题打开新的子类化框架。

最后一点,记住,从记忆中看来,重要的是后代框架(TcfEditFrame)是添加组件的那个,不能在 TcfBaseEditFrame 添加组件而在 TcfEditFrame 添加属性。

BaseEditFrames.pas

单位 BaseEditFrames;
接口
使用 Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms, 对话框;
类型 TBaseEditFrame = 类(TFrame) private {私有声明} FNewFormProperty: string; published {发布声明} property NewFormProperty: string read FNewFormProperty write FNewFormProperty; end;
实现
{$R *.dfm}
结束。

BaseEditFrames.dfm

对象 BaseEditFrame: TBaseEditFrame 左 = 0 上 = 0 宽度 = 320 高度 = 240 TabOrder = 0 end

EditFrames.pas

单元编辑框架;
接口
使用 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ActnList, BaseEditFrames;
类型 TEditFrame = class(TBaseEditFrame) ActionList: TActionList; private { 私有声明 } public { 公共声明 } end;
实现
{$R *.dfm}
结束。

EditFrames.dfm

对象 EditFrame:TEditFrame 左 = 0 上 = 0 宽度 = 320 高度 = 240 TabOrder = 0 对象 ActionList:TActionList 左 = 72 上 = 16 结束 结束

FramePackage.dpk

包FramePackage;
{$R * .res} {$ ALIGN 8} {$ ASSERTIONS ON} {$BOOLEVAL OFF} {$DEBUGINFO ON} {$EXTENDEDSYNTAX ON} {$IMPORTEDDATA ON} {$IOCHECKS ON} {$LOCALSYMBOLS ON} {$LONGSTRINGS ON} {$OPENSTRINGS ON} {$OPTIMIZATION ON} {$OVERFLOWCHECKS OFF} {$RANGECHECKS OFF} {$REFERENCEINFO OFF} {$SAFEDIVIDE OFF} {$STACKFRAMES OFF} {$TYPEDADDRESS OFF} {$VARSTRINGCHECKS ON} {$WRITEABLECONST OFF} {$MINENUMSIZE 1} {$IMAGEBASE $ 400000} {$ DESCRIPTION'可继承的框架'} {$ IMPLICITBUILD ON}
要求   rtl,   vcl,   designide;
包含   在'RegisterFramePackage.pas'中注册帧包,   在“BaseEditFrames.pas”中的BaseEditFrame:TFrame,   在'EditFrames.pas'中的EditFrame:TFrame。
结束。
单位RegisterFramePackage;
接口
过程登记;
实施
使用类,DesignIntf,WCtlForm,BaseEditFrames;
过程登记; 开始   RegisterCustomModule(TBaseEditFrame,TWinControlCustomModule); 结束;
结束。
你需要安装此设计时包。然后你可以在另一个项目中拥有一个如下的框架。

EditFrameDescendants.pas

单元 EditFrameDescendants;
接口 uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, EditFrames, ActnList;
type TEditFrameDescendant = class(TEditFrame) Action1: TAction; private { Private declarations } public { Public declarations } end;
实现
{$R *.dfm}
end.

EditFrameDescendants.dfm

继承自TEditFrameDescendant的EditFrameDescendant: ParentFont = False 继承自TActionList的ActionList: object Action1: TAction Caption = 'Action1' end end end

您应该能够打开EditFrameDescendant,编辑其惊人的“NewFormProperty”,并将操作添加到其操作列表中。对我来说很有效...祝好运


1
我不理解CodeSimth在你的解决方案中扮演的角色。你能否稍微解释一下?我也不明白这个解决方案如何帮助继承:我绝对需要从框架中继承,而不仅仅是使用它们。谢谢。 - Cosmin Prund
CodeSmith设置了一个模板,用于创建我们框架的子类,包括.pas和.dfm文件。因此,以我们的示例为例,我们有一个cfEditFrame.pas.cst(CodeSmith模板),cfEditFrame.dfm.cst文件,它们生成了相应的文件,以及一个cfEditFrame.cst文件,它实际上调用了两个模板,并使用相同的参数将这些文件写入磁盘。我已经在原始回复中添加了两个模板的基本版本。希望现在能够理解。 - Clint Good
只是为了让其他人更明确,我们从不使用“文件|新建”来创建子类,而是使用CodeSmith模板生成文件,然后将它们添加到适当的包/项目中。 - Clint Good
啊哈。所以你本质上是在使用“运行时包”,这与Trinidad的解决方案相同。 - Cosmin Prund
我们确实像Cosmin一样只在设计时使用包。我们编译一个单体可执行文件(不使用包进行构建)。设计时包仅用于支持IDE。这就是为什么我们在设计时可能使用稍旧的(甚至来自不同分支的)包构建,也没有关系。 - Clint Good
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接