我正在尝试根据用户设置配置编写一个返回两个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();
答案 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_FooBus
和TfrmRAM_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;