如何编写在运行时返回现有TForm实例的函数?

时间:2014-12-01 08:33:51

标签: delphi delphi-7

我正在尝试根据用户设置配置编写一个返回两个TForm实例之一的函数:

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;

我需要这个函数,因为这个单元(Main.pas)在RAM编辑器表单中读/写了很多变量。

编译器在以下行上跳闸:

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

错误消息:Undeclared identifier 'StatusBar1'

如果我明确提供TForm实例,则没有错误:

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

StatusBar在这两种形式中都是这样声明的:

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

有趣的是,编译器不介意以下内容:

GetCurrentRamEditFrm().show();

3 个答案:

答案 0 :(得分:10)

您的函数将实例作为TForm返回,该实例对您在StatusBar1中声明的TfrmRAM_Editor_SXcp一无所知。

GetCurrentRamEditFrm().show();有效,因为TForm类有方法Show

您必须创建基本表单类型,声明您要使用的所有变量和方法,或声明两个表单将共享的接口。

解决方案1:

type
  TBaseForm = class(TForm)
    StatusBar1: TStatusBar;

type
  TfrmRAM_Editor_SXcp = class(TBaseForm)
    // this type will automatically inherit StatusBar1

function TfrmMain.GetCurrentRamEditFrm: TBaseForm;
{ 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;

解决方案2:

type
  IBaseForm = interface
    procedure SetStatus(const s: string);
  end;

type
  TfrmRAM_Editor_SXcp = class(TForm, IBaseForm)
    StatusBar1: TStatusBar; // i.e. the scope is "published"
    ...
    procedure SetStatus(const s: string);

procedure TfrmRAM_Editor_SXcp.SetStatus(const s: string);
begin
  StatusBar1.Panels[1].Text := s;
end;

function TfrmMain.GetCurrentRamEditFrm: IBaseForm;
{ 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;

然后您可以像这样使用它:

GetCurrentRamEditFrm().SetStatus(get_text(96));

当然,即使你没有使用interfaced解决方案,最好引入你需要的功能方法,而不是直接抓取像StatusBar这样的UI元素。如果有一天你需要使用接口而不是公共基类,如果你已经有了方法,那么引入接口将非常容易。

答案 1 :(得分:6)

编译器错误非常容易理解,因为TForm没有名为StatusBar1的成员。您在派生表单中介绍了它,我认为它是TfrmRAM_Editor_FooBusTfrmRAM_Editor_SXcp类型。

现在,如果这两个表单派生自引入StatusBar1的公共基础,则可以返回该公共基类,并且您的代码将进行编译。这看起来像这样:

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;

然而,这并不是一个很好的解决方案。我遇到的问题是继承是一种非常严格的机制,我不喜欢将UI控件暴露在表单本身之外。意识到Delphi的流媒体机制迫使设计时控件被发布,因此可以从外部看到,但在我看来,这是一个可怕的错误,促进了糟糕的设计。

我个人定义了一个可用于设置状态文本的界面。

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

让每个表单实现此接口,然后您可以使用as查询它。或者更好,让你的GetCurrentRamEditFrm函数返回界面而不是表单。

这可以避免您不得不将表单的UI实现细节公开给所有人。

答案 2 :(得分:1)

您可以使用RTTI为您完成此操作。使用RTTI执行此操作的一个优点是您无需更改继承或应用接口。您只需要在表单上设置已发布的属性

type
  TfrmRAM_Editor_SXcp = class(TForm)
  published
    property StatusBar: TStatusBar read FStatusBar write FStatusBar;
    //property StatusBar: TStatusBar read StatusBar1 write StatusBar1; //Alternative
    ...
  end;

constructor TfrmRAM_Editor_SXcp.Create(AOwner: TComponent);
begin
  FstatusBar := StatusBar1;
end;  

您可以使用RTTI访问状态栏

function TfrmMain.GetRAMFrmStatusBar: TStatusBar;
begin
  if IsPublishedProp(GetCurrentRamEditFrm, 'StatusBar') then
    result :=  GetObjProp(GetCurrentRamEditFrm(), 'StatusBar') as TStatusBar
  else
    result := nil;
end;

或者创建一个setStatusBarText过程

procedure TfrmMain.setRAMFrmStatusBarText(const panelId: integer; const text: string);
begin
  GetRAMFrmStatusBar.Panels[panelId].Text := text;
end;