4

I have ported an application from ADO to FireDAC applying several RegExp replaces on the source code to convert the ADOQuery, ADOTables, ADOCommands, ADOStoredProcs, etc. ... to the corresponding FireDAC components.

It has worked fine, but now when running that application plenty of forms raise errors because of the type of the persistent fields being different than the type expected (the one defined from ADO when the persistent field was created).

I'm trying to make a list of those errors, creating an instance of all my forms and opening their datasets with persistent fields, and logging the errors. I can get the list of forms from the project source code, but when I try to use FindClass to create each form I get an error telling that the class has not been found.

Is there any other way to create a Form/DataModule from its class name ?.

This is my current code:

class procedure TfrmCheckFormularis.CheckDatasets(ProjecteFile: string);
var frmCheckFormularis: TfrmCheckFormularis;
    Projecte: string;
    rm: TMatch;
    cc: TComponentClass; 
    c: TComponent;
    i: integer;
    Dataset: TFDQuery;
begin
  Projecte := TFile.ReadAllText(ProjecteFile);
  frmCheckFormularis := TfrmCheckFormularis.Create(Application);
  try
    with frmCheckFormularis do begin
      Show;
      qryForms.CreateDataSet;
      qryErrors.CreateDataSet;
      // I get a list of all the forms and datamodules on my project
      for rm in TRegEx.Matches(Projecte, '^(?:.* in '')(?<File>.*)(?:'' {)(?<Class>.*)(?:},)', [roMultiline]) do begin
        qryForms.AppendRecord([rm.Groups['File'].Value, rm.Groups['Class'].Value]);
      end;

      // Check every form and datamodule
      qryForms.First;
      while not qryForms.Eof do begin
        cc := TComponentClass(FindClass(qryFormsClass.Value));
        c := cc.Create(frmCheckFormularis);
        try
          for i := 0 to c.ComponentCount - 1 do begin
            if c.Components[i] is TFDQuery then begin
              Dataset := c.Components[i] as TFDQuery;
              // When the Dataset has persistent fields, I open it to check if the persistent fields are correct
              if Dataset.FieldDefs.Count > 1 then begin
                try
                  Dataset.Open;
                except
                  on E: Exception do qryErrors.AppendRecord([c.Name, Dataset.Name, E.Message]);
                end;
              end;
            end;
          end;
        finally
          c.Free;
        end;
        qryForms.Next;
      end;
    end;
  finally
    frmCheckFormularis.Free;
  end;
end;

Thank you.

Marc Guillot
  • 6,090
  • 1
  • 15
  • 42
  • 1
    Interesting q, +1. I wonder if you could get a list of the forms by reading the .Exe's resource stream? Not sure that would work with datamodules, though. – MartynA Aug 28 '20 at 11:33
  • I'd try to do it with RTTI. – Andreas Rejbrand Aug 28 '20 at 11:38
  • @AndreasRejbrand, I've never used the RTTI before, can you address me to a tutorial or some documentation where to start ?. Thank you. – Marc Guillot Aug 28 '20 at 11:44
  • When you open the offending forms in the IDE, do they trigger an error? – fpiette Aug 28 '20 at 11:46
  • @fpiette No, opening the forms doesn't trigger an error, I have to open the datasets on the IDE for the components read the result and raise an error that some of the persistent fields are of a different type. – Marc Guillot Aug 28 '20 at 11:48
  • You get a list of all forms in your project. Why not saving this to a text file and then using a notepad++ (Or any other text editor, including Delphi itself), create a Delphi source code which open the form? – fpiette Aug 28 '20 at 11:51
  • @J..., yes, I also applied the changes on the dfms. But some incompatibilities can't be detected by the script. For example, TDate fields were recognized by ADO as ftWideString fields, so I don't have any way to differentiate them and correct them on the script. I need to check every dataset to find all those errors. – Marc Guillot Aug 28 '20 at 11:51
  • @MarcGuillot If your forms were generated normally and still all have their auto-created designer global instance variables you could just move all of your forms to the auto-create list in the project settings. Run your program once, then move them back. – J... Aug 28 '20 at 11:54
  • @fpiette I was hoping to find a solution entirely at runtime, so I could use it in the future to make other checks over the forms without leaving the application. But thanks, I would do as you say if I can't find a solution at runtime. – Marc Guillot Aug 28 '20 at 11:56

1 Answers1

9

Using the "new" RTTI in Delphi is quite easy. The following code will (hopefully*) create one instance of each form in your application:

procedure TForm1.Button1Click(Sender: TObject);
var
  Context: TRttiContext;
  &Type: TRttiType;
  InstanceType: TRttiInstanceType;
begin
  Context := TRttiContext.Create;
  for &Type in Context.GetTypes do
  begin
    if (&Type.TypeKind = tkClass) and &Type.IsInstance then
    begin
      InstanceType := TRttiInstanceType(&Type);
      if InstanceType.MetaclassType.InheritsFrom(TForm) and (InstanceType.MetaclassType <> TForm) then
        TFormClass(InstanceType.MetaclassType).Create(Application){.Show}; // optionally show it
    end;
  end;
end;

* Technically, it will create one instance of each proper descendant class of TForm.

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384