I have an FMX application (but should be the same in VCL) with a TabControl showing 10 tabs. The tabs are set to visible or not visible depending on application state and user rights.
It works well, but I don't like
that everying is together and muddled up in the main form
and tab contents are initialized even if they never become visible.
So I thought about using frames which are created when their tab becomes visible.
Each frame can only exist once and it should be easily possible to manipulate one frame from another (access controls on the other frame).
I like elegant solutions and short code :)
This is what I already found, quite nice but it is very old: Replacing TabSheets with Frames - by Dan Miser
This solution has a global variable for each frame. All frames can use the main form unit in their implementation part and have easy access to other frames and all their controls, even without adding the other frames to the uses clause.
Tabs start invisible and their frame is uninitialized. TabA.Activate;
shows the tab and sets focus. TabA.Frame.Label1
easily accesses a control on that Frame. TabA.Visible:= False;
hides the tab and frees the frame.
Generics are really helpful here, I like it.
Ideas for improvements are very welcome...
type
TFormMain = class(TForm)
TabControl: TTabControl;
TabInfo: TTabItem;
procedure FormCreate(Sender: TObject);
private
procedure AddTab<T: TTabItem>(out Tab: T);
public
TabA: TTabItemFrame<TFrameA>;
TabB: TTabItemFrame<TFrameB>;
TabC: TTabItemFrame<TFrameC>;
end;
var
FormMain: TFormMain;
implementation
procedure TFormMain.FormCreate(Sender: TObject);
begin
AddTab(TabA);
AddTab(TabB);
AddTab(TabC);
TabA.Activate;
end;
procedure TFormMain.AddTab<T>(out Tab: T);
begin
Tab:= TabControl.Add(T) as T;
end;
---------------------------------------------------------------------
unit _FrameBase;
interface
uses
System.Classes, FMX.Forms, FMX.TabControl;
type
TFrameBase = class abstract(TFrame)
public
class function GetTitle: string; virtual; abstract;
end;
TTabItemFrame<T: TFrameBase> = class(TTabItem)
private
FFrame: T;
protected
procedure Hide; override;
procedure Show; override;
public
constructor Create(AOwner: TComponent); override;
function Activate: T;
property Frame: T read FFrame;
end;
implementation
{ TTabItemFrame }
constructor TTabItemFrame<T>.Create(AOwner: TComponent);
begin
inherited;
Text:= T.GetTitle;
Visible:= False;
end;
function TTabItemFrame<T>.Activate: T;
begin
Visible:= True;
TabControl.ActiveTab:= Self;
Result:= FFrame;
end;
procedure TTabItemFrame<T>.Hide;
begin
inherited;
FFrame.DisposeOf;
FFrame:= nil;
end;
procedure TTabItemFrame<T>.Show;
begin
inherited;
FFrame:= T.Create(Self);
FFrame.Parent:= Self;
end;
end.
---------------------------------------------------------------------
type
TFrameA = class(TFrameBase)
Label1: TLabel;
public
class function GetTitle: string; override;
end;
implementation
// if it's necessary to access components or methods of
// any other frame or the main form directly
uses
_FormMain;
//...
Update: I decided to use forms instead of frames for my FMX application because frames cannot use styles at design time. A side effect is that instead of the class function I can use the form caption for the tab title.
Embedding a form into a tabitem is a little tricky:
constructor TFormTabBase.Create(AOwner: TComponent);
begin
inherited;
while ChildrenCount > 0 do Children[0].Parent:= AOwner as TTabItem;
end;
procedure TTabItemForm<T>.Show;
begin
inherited;
FFormTab:= T.Create(Self);
Text:= FFormTab.Caption;
end;