Delphi:在 属性 编辑器中浏览组件

Delphi: browsing components inside a property editor

当 属性 是任何 class 的简单组件时,IDE 的 属性 编辑器能够下拉列表中的所有兼容组件所有项目的表格。

我想做一些等效的任务,但要根据可接受的组件 classes 对 属性 进行一些过滤;这些 classes 的共同祖先只是 TComponent 并且它们具有自定义接口。

目前我有一个可用的 属性 编辑器,它使用 paValueList 属性和 GetValues 过程中的一些过滤,基于检查支持的接口,但仅限于当前形式:-(.

如何像IDE那样浏览所有表格?

I want to do some equivalent task, but with some filtering based on acceptable component classes for the property; these classes common ancestor is only TComponent and they have custom interfaces.

如果您只过滤 1 个接口,您应该更改有问题的 属性 以接受该接口类型而不是 TComponent,然后更改默认的 属性 编辑器界面属性(TInterfaceProperty)会自动为您过滤组件:

property MyProperty: IMyInterface read ... write ...;

Currently I have a working property editor that uses a paValueList attribute and some filtering in the GetValues procedure, based on checking the supported interfaces, but it is limited to the current form :-(.

How to browse all the forms like the IDE does?

要在自定义 属性 编辑器中手动过滤组件,您需要执行与默认组件 属性 编辑器 (TComponentProperty) 相同的操作来获取兼容组件, 然后您可以根据需要进一步过滤它们。

在内部,TComponentProperty.GetValues() 只是调用 Designer.GetComponentNames(),将其传递给正在编辑的 属性 类型的 PTypeData

procedure TComponentProperty.GetValues(Proc: TGetStrProc);
begin
  Designer.GetComponentNames(GetTypeData(GetPropType), Proc);
end;

因此,如果您的 属性 接受 TComponent(因为这是您预期组件的唯一共同祖先):

property MyProperty: TComponent read ... write ...;

那么 GetPropType() 在这种情况下会 return TypeInfo(TComponent).

GetComponentNames()(其实现在 IDE 中,在 VCL 源代码中不可用)枚举拥有正在编辑的组件的 Root(Form、DataModule 或 Frame)的组件,以及所有链接的 Root 对象,这些对象可以在编辑的 Root 的 uses 子句中指定的其他单元中访问。这是记录在案的行为:

DesignIntf.IDesigner60.GetComponentNames

Executes a callback for every component that can be assigned a property of a specified type.

Use GetComponentNames to call the procedure specified by the Proc parameter for every component that can be assigned a property that matches the TypeData parameter. For each component, Proc is called with its S parameter set to the name of the component. This parameter can be used to obtain a reference to the component by calling the GetComponent method.

Note: GetComponentNames calls Proc for components in units that are in the uses clause of the current root object's unit (Delphi) or included by that unit (C++), as well as the entity that is the value of Root.

因此,在您的 GetValues() 实施中,调用 Designer.GetComponentNames()TComponent 指定 PTypeData 并让 IDE 枚举所有可用单位并为您提供带有每个组件名称的列表。然后你可以遍历该列表调用 Designer.GetComponent() 来获取实际的 TComponent 对象并查询它们以获得你想要的接口:

procedure TMyComponentProperty.GetValues(Proc: TGetStrProc);
var
  Names: TStringList;
  I: Integer;
begin
  Names := TStringList.Create;
  try
    Designer.GetComponentNames(GetTypeData(TypInfo(TComponent)), Names.Append);
    for I := 0 to Names.Count-1 do
    begin
      if Supports(Designer.GetComponent(Names[I]), IMyInterface) then
        Proc(Names[I]);
    end;
  finally
    Names.Free;
  end;
end;

事实上,这与默认的 TInterfaceProperty.GetValues() 实现非常相似:

procedure TInterfaceProperty.ReceiveComponentNames(const S: string);
var
  Temp: TComponent;
  Intf: IInterface;
begin
  Temp := Designer.GetComponent(S);
  if Assigned(FGetValuesStrProc) and
     Assigned(Temp) and
     Supports(TObject(Temp), GetTypeData(GetPropType)^.Guid, Intf) then
    FGetValuesStrProc(S);
end;

procedure TInterfaceProperty.GetValues(Proc: TGetStrProc);
begin
  FGetValuesStrProc := Proc;
  try
    Designer.GetComponentNames(GetTypeData(TypeInfo(TComponent)), ReceiveComponentNames);
  finally
    FGetValuesStrProc := nil;
  end;
end;

唯一的区别是 TInterfaceProperty 不会浪费内存将名称收集到临时 TStringList 中。它在枚举它们时实时过滤它们。

Remy 的解决方案非常适合我的需要。 不过我"simplified"有点过滤程序:

procedure TMyComponentProperty.ReceiveComponentNames(const S: string);
var
  Temp: TComponent;
  Intf: IInterface;
begin
  if Assigned(FGetValuesStrProc) then
    begin
      Temp := Designer.GetComponent(S);
      if Assigned(Temp) then
        if Temp.GetInterface(IMyInterface, IntF) then
          FGetValuesStrProc(S);
      // May add other interfaces checks here   
    end;
end;