Reputation: 5029
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();
Upvotes: 1
Views: 345
Reputation: 185
You can use RTTI to do that for you. One advantage of doing this with RTTI is that you do not need to change inheritance or apply interfaces. You will just need to set a published property on the forms
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;
With that you can access the status bar using RTTI
function TfrmMain.GetRAMFrmStatusBar: TStatusBar;
begin
if IsPublishedProp(GetCurrentRamEditFrm, 'StatusBar') then
result := GetObjProp(GetCurrentRamEditFrm(), 'StatusBar') as TStatusBar
else
result := nil;
end;
Or create a setStatusBarText procedure
procedure TfrmMain.setRAMFrmStatusBarText(const panelId: integer; const text: string);
begin
GetRAMFrmStatusBar.Panels[panelId].Text := text;
end;
Upvotes: 1
Reputation: 28516
Your function returns instance as TForm
that does not know anything about StatusBar1
you have declared in TfrmRAM_Editor_SXcp
.
GetCurrentRamEditFrm().show();
works because TForm
class has method Show
.
You would have to either create base form type that will declare all variables and methods you want to use or declare interface that both of your forms will share.
Solution 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;
Solution 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;
You can then use it like this:
GetCurrentRamEditFrm().SetStatus(get_text(96));
Of course, even if you don't go with interfaced solution, it would be good to introduce methods for functionality you need instead of grabbing UI elements like StatusBar
directly. And if some day you do need to use interfaces instead of common base class if you already have methods in place then introducing interface would be very easy.
Upvotes: 10
Reputation: 612954
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.
Upvotes: 6