How to determine the path of a DFM where the custom TComponent is?

114 views Asked by At

Within a custom TComponent, I need to get the path of the *.dfm or *.pas file where the TComponent is used. Obviously, this is only required at design-time.

Context

Similar to a previous question:
How to detect the dfm/project path from a custom control while in the designer?

I created a custom TControl similar to TImageCollection but with a shared database of images for all the application. For this, the control reads a project meta-file which is an XML containing the list of images to include in the image collection. When in the designer, images are read directly from the image files, but at runtime, images are in the resource (*.res) file generated during the compilation.

if csDesigning in Self.ComponentState then
  loadImagesFromFiles(...)
else
  loadImagesFromResource(...)

In the previous version (for the other question linked above), I tried to get the path of the project, and while this seems not possible, I found a way to get the path to the active project. However, this is very problematic when the IDE has several projects open.

I decided to switch to another approach, where I use a path relative to the DFM where the component is used. This should be more reliable.

Unfortunately, it seems not that easy for a custom TComponent placed on a DFM to know where that DFM is located.

Solutions (half)

I tried several solutions with IOTA, but could not get anything working yet.

The approach provided here returns the path to the TComponent itself, not to the DFM where it's used.

I managed to iterate Modules following the same strategy, but finding which Module contains the given TComponent is not trivial:

function GetCurrentDfmPath(AComponent: TComponent): String;
var
  ModuleServices: IOTAModuleServices;
  Module: IOTAModule;
  idx: integer;
begin
  Result := '';
  SourceEditor := nil;

  if SysUtils.Supports(BorlandIDEServices, IOTAModuleServices,
    ModuleServices) then
  begin
    for idx := 0 to ModuleServices.ModuleCount - 1 do
    begin
      Module := ModuleServices.Modules[idx];

      // Needs to match the module that contains AComponent.?

    end;
  end;
end;
2

There are 2 answers

3
Remy Lebeau On BEST ANSWER

At design-time, your component's Owner should be the parent Form (all components dropped on a Form at design-time are owned by the Form by default).

If your component is derived from TControl, it can alternatively find its parent Form by using the Forms.GetParentForm() function.

TCustomForm has a Designer property of type IDesignerHook that is non-nil at design-time.

Query the IDesignerHook for IDesigner using the as operator (do not use a hard cast!).

IDesigner has a ModuleFileNames() method to retrieve the filenames associated with the object being designed, which in this case will be the TCustomForm so the FormFileName parameter will output the filename of the .dfm (VCL) or .xfm (FMX).

0
Adrian Maire On

After some research, and still having some issues within DataModules, I finally implemented a solution like the following:

function GetCurrentDfmPath(aComponent: TComponent): string;
var
  form: TCustomForm;
  parent: TComponent;
  designerHook: IDesignerHook;
  impl, intf, formFile: string;

  ModuleServices: IOTAModuleServices;
  Module: IOTAModule;
  Editor: IOTAFormEditor;
  i: Integer;
begin

  // Test if we can get the IDesigner
  parent := aComponent.owner;
  while Assigned(parent.owner) and not (parent is TCustomForm) do
  begin
    parent := parent.owner;
  end;

  if Assigned(parent) and (parent is TCustomForm) then
  begin
    form := parent as TCustomForm;

    designerHook := form.Designer;
    if Assigned(designerHook) and Assigned(designerHook as IDesigner) then
    begin
      (designerHook as IDesigner).ModuleFileNames(impl, intf, formFile);
      Result := formFile;
    end;
  end;

  // If not found (maybe we are using a DataModule), attempt another solution.
  if (Result='') then
  begin
    parent := aComponent.owner;
    while Assigned(parent.owner) and not (parent is TDataModule) do
    begin
      parent := parent.owner;
    end;

    if Supports(BorlandIDEServices, IOTAModuleServices, ModuleServices) then
    begin
      // Iterate through all modules open in the IDE
      for i := 0 to ModuleServices.ModuleCount - 1 do
      begin
        Module := ModuleServices.Modules[i];
            
        if SameText(ExtractFileName(Module.FileName), parent.UnitName  + '.pas') then
        begin
          Result := Module.FileName;
          Break;
        end;
      end;
    end;
  end;

  Result := ExtractFilePath(Result);
end;