Search code examples
delphidelphi-10-seattle

Calling class functions of different forms


I have an application which includes the "main" form as well as four other forms (formA, formB, formC, formD). Each one of the four forms has a unique class function execute().

Now, in formD, I put an edit box and a button. On the button's OnClick event, depending on which form's name I pass in the edit box, I want to run the appropriate class function.

I tried to implement this task by creating a TDictionary in which I add 3 pairs of values, but it didn't work. Specifically I did the following:

unit FormDU;

interface

type
  TFormD = class(TForm)
  Edit1: TEdit;
  fcShapeBtn1: TfcShapeBtn;
  .............
  .............
public
  class function execute:boolean;
  ..........
  ..........
end;

var
  FormD: TFormD;
  MyList:TDictionary<string,TForm>;


implementation 

class function TFormD.execute:boolean;
  begin
    FormD:= TFormD.Create(nil);
    MyList:= TDictionary<string,TForm>.create;
    MyList.Add('FormA',TFormA);
    MyList.Add('FormB',TFormB);
    MyList.Add('FormC',TFormC);
    FormD.showmodal;
  end;

procedure TFormD.fcShapeBtn1Click(Sender: TObject); 
begin
  // Here I check whether the text in Edit1 box has the value of one of the
  // keys that are included in the MyList dictionary and if yes I want to 
  // trigger the class function execute of the appropriate form...

  if MyList.ContainsKey(Edit1.text) then  // suppose that text= formA
      MyList.Items[Edit1.text].execute // which doesn't work.... 

  // I thought that the 'Items' method of the dictionary would return back 
  // to me the appropriate form type - which is connected to the specific 
  // key - and thus I could call each form's class function execute()
end;

I don't know how to solve this problem.


Solution

  • There are two problems with your approach:

    1. your TDictionary is declared to hold TForm object pointers, but your code is trying to insert TForm-derived class types instead. That will not compile.

    2. Your Form classes do not derive from a common base class that has an Execute() method for them to override. So you can't just retrieve a value from your TDictionary and call Execute directly. You would have to resort to using RTTI to find and invoke Execute() instead.

    There are some possible ways to address this:

    1. derive your Form classes from a common base class, and store derivatives of that base class in your TDictionary:

      unit FormBaseU;
      
      interface
      
      uses
        Forms;
      
      type
        TFormBase = class(TForm)
        public
          class function Execute: Boolean; virtual;
        end;
      
        TFormBaseClass = class of TFormBase;
      
      implementation
      
      class function TFormBase.Execute: Boolean;
      begin
        Result := False;
      end;
      
      end.
      

      unit FormDU;
      
      interface
      
      uses
        ..., FormBaseU;
      
      type
        TFormD = class(TFormBase)
          Edit1: TEdit;
          fcShapeBtn1: TfcShapeBtn;
          ...
        public
          class function Execute: Boolean; override;
          ...
        end;
      
      var
        FormD: TFormD;
        MyList: TDictionary<string, TFormBaseClass>;
      
      implementation 
      
      uses
        FormAU, FormBU, FormCU;
      
      class function TFormD.Execute: Boolean;
      begin
        MyList := TDictionary<string, TFormBaseClass>.Create;
      
        // make sure TFormA, TFormB, and TFormC all derive
        // from TFormBase and override Execute() ...
        MyList.Add('FormA', TFormA);
        MyList.Add('FormB', TFormB);
        MyList.Add('FormC', TFormC);
      
        FormD := TFormD.Create(nil);
        FormD.ShowModal;
        FormD.Free;
      end;
      
      procedure TFormD.fcShapeBtn1Click(Sender: TObject); 
      var
        FormClass: TFormBaseClass;
      begin
        if MyList.TryGetValue(Edit1.Text, FormClass) then
          FormClass.Execute;
      end;
      
    2. do something similar but using an interface instead of a base class (this only works with objects, not class types, though):

      unit MyIntfU;
      
      interface
      
      type
        IMyIntf = interface
          ['{41BEF2B6-C27F-440E-A88B-9E5CF8840034}']
          function Execute: Boolean;
        end;
      
      implementation
      
      end.
      

      unit FormDU;
      
      interface
      
      uses
        ..., MyIntfU;
      
      type
        TFormD = class(TForm, MyIntf)
          Edit1: TEdit;
          fcShapeBtn1: TfcShapeBtn;
          ...
        public
          function Execute: Boolean;
          ...
        end;
      
      var
        FormD: TFormD;
        MyList: TDictionary<string, TForm>;
      
      implementation 
      
      uses
        FormAU, FormBU, FormCU;
      
      function TFormD.Execute: Boolean;
      begin
        MyList := TDictionary<string, TForm>.Create;
      
        // make sure TFormA, TFormB, and TFormC are all
        // instantiated beforehand and implement IMyIntf ...
        MyList.Add('FormA', FormA);
        MyList.Add('FormB', FormB);
        MyList.Add('FormC', FormC);
      
        FormD := TFormD.Create(nil);
        FormD.ShowModal;
        FormD.Free;
      end;
      
      procedure TFormD.fcShapeBtn1Click(Sender: TObject); 
      var
        Form: TForm;
        Intf: IMyIntf;
      begin
        if MyList.TryGetValue(Edit1.Text, Form) then
        begin
          if Supports(Form, IMyIntf, Intf) then
            Intf.Execute;
        end;
      end;
      
    3. don't store classes/objects in your TDictionary at all, store the actual class methods instead:

      unit FormDU;
      
      interface
      
      uses
        ...;
      
      type
        TFormD = class(TForm)
          Edit1: TEdit;
          fcShapeBtn1: TfcShapeBtn;
          ...
        public
          class function Execute: Boolean;
          ...
        end;
      
        TMyClassMethod = function: Boolean of object;
      
      var
        FormD: TFormD;
        MyList: TDictionary<string, TMyClassMethod>;
      
      implementation 
      
      uses
        FormAU, FormBU, FormCU;
      
      class function TFormD.Execute: Boolean;
      begin
        MyList := TDictionary<string, TMyClassMethod>.Create;
      
        MyList.Add('FormA', TFormA.Execute);
        MyList.Add('FormB', TFormB.Execute);
        MyList.Add('FormC', TFormC.Execute);
      
        FormD := TFormD.Create(nil);
        FormD.ShowModal;
        FormD.Free;
      end;
      
      procedure TFormD.fcShapeBtn1Click(Sender: TObject); 
      var
        Meth: TMyClassMethod;
      begin
        if MyList.TryGetValue(Edit1.Text, Meth) then
          Meth;
      end;