Search code examples
delphifiremonkeytabcontrolvcltframe

How to have each tab of a TTabControl in its own unit / use TFrame instead of tabs?


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


Solution

  • 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;