Philippe
Philippe

Reputation: 23

Delphi: browsing components inside a property editor

When a property is a simple component of any class, the IDE's property editor is able to drop down a list of all compatible components in all the project's forms.

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.

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?

Upvotes: 2

Views: 758

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 595981

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.

If you are filtering for only 1 interface, you should change the property in question to accept that interface type instead of a TComponent, and then the default property editor for interface properties (TInterfaceProperty) will filter the components automatically for you:

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?

To manually filter the components in a custom property editor, you need to do the same thing that the default component property editor (TComponentProperty) does to obtain the compatible components, and then you can filter them further as needed.

Internally, TComponentProperty.GetValues() simply calls Designer.GetComponentNames(), passing it the PTypeData of the property type that is being edited:

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

So, if your property accepts a TComponent (since that is the only common ancestor of your intended components):

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

Then GetPropType() in this case would return TypeInfo(TComponent).

GetComponentNames() (whose implementation is in the IDE and not available in the VCL source code) enumerates the components of the Root (Form, DataModule, or Frame) that owns the component being edited, as well as all linked Root objects that are accessible in other units specified in the edited Root's uses clause. This is documented behavior:

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.

So, in your GetValues() implementation, call Designer.GetComponentNames() specifying the PTypeData for TComponent and let the IDE enumerate all available units and provide you with a list of each component's Name. Then you can loop through that list calling Designer.GetComponent() to get the actual TComponent objects and query them for your desired interface(s):

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;

In fact, this is very similar to what the default TInterfaceProperty.GetValues() implementation does:

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;

The only difference is that TInterfaceProperty does not waste memory collecting the names into a temp TStringList. It filters them in real-time as they are being enumerated.

Upvotes: 4

Philippe
Philippe

Reputation: 23

Remy's solution works perfectly for my needs. Nevertheless I've "simplified" a bit the filtering procedure:

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;

Upvotes: 0

Related Questions