为啥我的组件会自动将其他单元添加到使用界面?

Posted

技术标签:

【中文标题】为啥我的组件会自动将其他单元添加到使用界面?【英文标题】:Why is my Component auto adding other units to the uses interface?为什么我的组件会自动将其他单元添加到使用界面? 【发布时间】:2013-01-17 14:13:14 【问题描述】:

我一直在写一些自己的自定义组件,有些是简单地从其他组件如TCustomButton、TCustomListBox等派生出来的。

假设我有TMyButton = class(TCustomButton),它位于一个名为 MyButton 的单元中,我已将该组件注册到打包并安装到 IDE 中。

现在我将创建一个新的空项目并将 TMyButton 拖放到表单中。当我编译项目时,它会自动将这些单元添加到接口部分:

.., StdCtrls, MyButton;

我当然希望 MyButton 会被添加,但希望 StdCtrls 不会。

这还不错,但我的其他一些组件更糟糕,例如一个源自TCustomActionMainMenuBar,当我将它添加到我的表单并编译时,我会添加这些额外的单元:

.., ToolWin, ActnMan, ActnCtrls, ActnMenus, MyMenu;

我想创建自己的组件的原因之一是为了防止将这么多的单元名称添加到界面部分,我想自己绘制并更改它们的默认属性等。

当我在表单中添加 3 或 4 个组件时,会自动添加额外的 6-10 个单元名称,我不希望这种情况发生。

所以我的问题 - 是否可以阻止 IDE 自动将单元名称添加到接口部分?

在我自己的组件源的实际使用界面中,我已经有了“不需要的”单元名称,我认为这已经足够了。我的组件知道它们需要哪些单元,那么为什么表单的源文件也需要知道/允许包含名称?

我只想自动添加MyButton, MyMenu;,而不是添加所有其他常见的单位名称。

【问题讨论】:

我认为需要添加它们,因为您的组件依赖于它们 - 如果您手动删除这些单元,它会起作用吗?它还会编译吗? @Jeff 如果我删除它们,它们只会在下一次编译时回来。事实上,这些单元已经在我的组件源中,我认为足以让 Delphi 知道不要将它们添加到表单单元中。 如果您尝试通过代码(myButton := TMyButton.Create(); )创建组件,只包含您自己的单元,那会怎样? @Jeff 当然这不会让它们神奇地出现。 OP 所做的全部目的是从调色板中删除视觉控件。 @Jeff:不,如果控件是在代码中创建的,它们就不会神奇地出现。代码要么无法编译,要么在运行时出现异常。如果不需要,IDE 不会将这些单元放在那里。 【参考方案1】:

您的组件很有可能是从已注册TSelectionEditor 派生实现(请参阅RegisterSelectionEditor())的其他组件派生的,这些实现覆盖了虚拟TSelectionEditor.RequiresUnits() 方法以将所需的单元插入uses 子句。这样做的一个原因是,如果这些组件定义了依赖于其他单元中的类型的属性/事件。

【讨论】:

实际上,您在回答中通过示例证明了我试图解释何时需要 TSelectionEditor 而无需借助 OTA。我知道 TSelectionEditor 是如何工作的,因为我必须自己创建一些实现以在 Indy 中使用。 恕我直言,您没有(试图)解释何时需要 TSelectionEditor,我也没有评论您解释 TSelectionEditor 的工作原理(我假设您知道)。我试图说明的一点是 TSelectionEditor 和 OTA 都不需要将单元添加到 uses 子句中,而您的回答表明确实如此。或者更好:没有解释为什么在没有特定 TSelectionEditor 实现的情况下添加单元。 (另一方面,IDE 内部可能确实使用了 RegisterSelectionEditor,这可能是 RegisterComponentsProc 指向的例程的一部分,但它是 Borland 的魔法,我们根本无法确定。) 我尝试了其他一些第三方组件,它们也都添加了额外的单元引用。我想我不应该尝试更改它,因为 IDE 坚持要添加它们。我希望将使用列表保持在最少的引用范围内,但没关系。【参考方案2】:

tl;dr;

无法阻止添加这些单位,您应该不再关心它。


我的组件知道它们需要哪些单元,那么为什么表单的源文件也需要知道名称?

你是对的,也是错的。当然,如果代码仅限于创建组件,那么只需要声明该组件的单元。运行时和设计时。但是当代码开发时,并且您想要实现需要来自祖先单元的类型的事件处理程序,那么您的代码需要在 uses 子句中使用这些单元。运行时和设计时。

示例:当从单元DBGrids 中删除TDBGrid 时,单元Grids 也会被添加,因为除其他外,State 参数的类型, TGridDrawState, 已发布的OnDrawDataCell 事件在祖先单元中声明。在设计器中双击该事件会导致添加以下处理程序:

procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect: TRect;
  Field: TField; State: TGridDrawState);
begin

end;

现在,由于TGridDrawState 的存在,这个源文件需要知道Grids 单元。

结论:可能有太多的单元用于小开发,但总有足够的单元用于实现所有已发布的事件。


我对它的实际工作原理做了一些研究。我已经投票给Remy's answer,因为没有它我不会想到这样做,但他实际上并不完全正确。

考虑以下示例单位:

unit AwLabel;

interface

uses
  Classes, StdCtrls;

type
  TAwLabelStyle = (bsWide, bsTall);

  TAwLabel = class(TLabel)
  private
    FStyle: TAwLabelStyle;
  published
    property Style: TAwLabelStyle read FStyle write FStyle default bsWide;
  end;

implementation

end.

unit AwLabelEx;

interface

uses
  Classes, AwLabel;

type
  TAwLabelEx = class(TAwLabel);

implementation

end.

unit AwReg;

interface

uses
  AwLabel, AwLabelEx;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TAwLabel, TAwLabelEx]);
end;

现在,如果您将TAwLabelEx 组件放在表单上,​​则会添加单元AwLabelAwLabelEx,这会自动发生。不需要特别的参与。 TAwLabelStyle 类型需要 AwLabel 单元。请注意,在这种情况下,它与事件无关。剩下的唯一论点是该类型用于组件定义的已发布部分。


如雷米所说,ISelectionEditor.RequiresUnits 怎么样?

考虑我们将TAwLabelStyle 移动到另一个单元:

unit AwTypes;

interface

type
  TAwLabelStyle = (bsWide, bsTall);

implementation

end.

当您现在将TAwLabelTAwLabelEx 组件拖放到表单上时,AwTypes 单元不会添加。引用最后一个链接:

注意:事件可能会使用一个类型,其参数之一既不在类单元中,也不在其任何祖先的单元中。在这种情况下,应该注册一个实现 RequiresUnits 的选择编辑器,并将其用于声明事件所需类型的每个单元。

那么,让我们注册一个选择编辑器:

unit AwReg;

interface

uses
  Classes, AwTypes, AwLabel, AwLabelEx, DesignIntf, DesignEditors;

type
  TAwLabelSelectionEditor = class(TSelectionEditor)
  public
    procedure RequiresUnits(Proc: TGetStrProc); override;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TAwLabel, TAwLabelEx]);
  RegisterSelectionEditor(TAwLabel, TAwLabelSelectionEditor);
end;

 TAwLabelSelectionEditor 

procedure TAwLabelSelectionEditor.RequiresUnits(Proc: TGetStrProc);
begin
  Proc('AwTypes');
end;

end.

现在在表单上删除TAwLabelTAwLabelEx 组件确实会导致将AwTypes 单元添加到uses 子句中;

【讨论】:

很好的答案!【参考方案3】:

TCustomActionMainMenuBar 的继承树如下所示:

System.Classes.TObject
System.Classes.TPersistent
System.Classes.TComponent
Vcl.Controls.TControl
Vcl.Controls.TWinControl
Vcl.ToolWin.TToolWindow
Vcl.ActnMan.TCustomActionBar
Vcl.ActnCtrls.TCustomActionDockBar
Vcl.ActnMenus.TCustomActionMenuBar
Vcl.ActnMenus.TCustomActionMainMenuBar
Vcl.ActnMenus.TActionMainMenuBar

当您在表单中包含组件时,此树中出现的所有单元都将被绘制到使用子句中 + 每次保存时都会重新插入。正如其他人所说,通过使用 Open Tools API,组件还可以包含不包含祖先的其他单元。

【讨论】:

【参考方案4】:

发生这种情况是因为您的组件的祖先使用Open Tools API 将一些单元添加到uses 子句中,代码如下:

uses
  ToolsAPI;

var
  currentProject: IOTAProject;
begin
  currentProject := GetActiveProject();
  currentProject.AddFile('StdCtrls.pas', True);

您可能还会发现this question 很有趣。

【讨论】:

问题是关于源文件的,但这个答案是针对项目文件的。 @NGLN 感谢您的编辑。老实说,我对OTA 几乎一无所知。 造成这种情况的不仅仅是 OTA。组件使用RegisterSelectionEditor() 注册一个TSelectionEditor 派生类会更简单,该类会覆盖虚拟RequiresUnits() 方法。这也会影响 IDE 插入到 uses 子句中的单元。

以上是关于为啥我的组件会自动将其他单元添加到使用界面?的主要内容,如果未能解决你的问题,请参考以下文章

使用自动布局将 UIButton 添加到自定义单元格

尝试使用自动布局将 UIButton 添加到我的视图中,但它没有出现。为啥? [关闭]

无法将 Tableview 单元格值添加到 JSON 参数,为啥会为零?迅速

为啥我的 Vue js 应用在路由到其他组件时会失去焦点?

Delphi:当组件放置在表单上时,错误的单元将单元添加到源文件的使用子句

DnD - 为啥拖动的标签显示在其他组件下方?