1
votes

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

1

1 Answers

0
votes

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;