Search code examples
delphidelphi-7

How to write function that returns an existing TForm instance at runtime?


I'm trying to write a function that returns either of two TForm instances, according to the user-set configuration:

function TfrmMain.GetCurrentRamEditFrm: TForm;
{ Get the RAM Editor Form instance according to currenttly-set protocol. }
begin
  if frmSetup.GetCurrentProtocol() = FooBus then
    result := RAM_Editor_FooBus.frmRAM_Editor_FooBus
  else
    result := RAM_Editor_SXcp.frmRAM_Editor_SXcp;
end;

I need this function because this unit (Main.pas) reads/writes a lot of variables in the RAM Editor Form.

The compiler trips up on lines like:

GetCurrentRamEditFrm().StatusBar1.Panels[1].Text := get_text(96);

with the error message: Undeclared identifier 'StatusBar1'

If I provide the TForm instance explicitely there is no error:

RAM_Editor_SXcp.frmRAM_Editor_SXcp.StatusBar1.Panels[1].Text := get_text(96);

StatusBar is declared like this, in both Forms:

type
  TfrmRAM_Editor_SXcp = class(TForm)
    StatusBar1: TStatusBar; // i.e. the scope is "published"
    ...

Interestingly, the compiler doesn't mind the following:

GetCurrentRamEditFrm().show();


Solution

  • The compiler error is quite understandable because TForm does not have a member named StatusBar1. You introduced that in your derived forms, which I presume are of type TfrmRAM_Editor_FooBus and TfrmRAM_Editor_SXcp.

    Now, if the two forms derive from a common base that introduces StatusBar1 you could return that common base class instead and your code would compile. This would look like this:

    type
      TfrmRAM_Editor_Base = class(TForm)
        StatusBar1: TStatusBar;
        ....
      end;
    
      TfrmRAM_Editor_FooBus = class(TfrmRAM_Editor_Base)
        ....
      end;
    
      TfrmRAM_Editor_SXcp = class(TfrmRAM_Editor_Base)
        ....
      end;
    
    function TfrmMain.GetCurrentRamEditFrm: TfrmRAM_Editor_Base;
    { Get the RAM Editor Form instance according to currently-set protocol. }
    begin
      if frmSetup.GetCurrentProtocol() = FooBus then
        result := RAM_Editor_FooBus.frmRAM_Editor_FooBus
      else
        result := RAM_Editor_SXcp.frmRAM_Editor_SXcp;
    end;
    

    However, that doesn't feel like a great solution. The problem I have with this is that inheritance is a very rigid mechanism, and I am not happy at all with exposing UI controls to be used outside the form itself. In realise that Delphi's streaming mechanism forces design-time controls to be published and therefore visible from the outside, but in my view that was a terrible mistake that promotes poor design.

    Personally I'd define an interface that can be used to set status text.

    type
      ISetStatusText = interface
        [...add GUID here]
        procedure SetStatusText(const Value: string);
      end;
    

    Have each form implement this interface and then you can query for it using as. Or perhaps better, have your GetCurrentRamEditFrm function return the interface rather than the form.

    This avoids your having to expose the form's UI implementation details to all and sundry.