如何让TTabControl的每个选项卡都在自己的单元/使用TFrame而不是选项卡?

时间:2022-12-02 17:54:59

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.

我有一个FMX应用程序(但在VCL中应该是相同的),它的TabControl显示10个选项卡。选项卡根据应用程序状态和用户权限设置为可见或不可见。

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

这是我已经找到的,很不错,但它非常古老:用帧替换选项卡——丹·米瑟(Dan Miser)著

1 个解决方案

#1


0  

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.

这个解决方案对每一帧都有一个全局变量。所有框架都可以在其实现部分中使用主窗体单元,并且可以方便地访问其他框架和它们的所有控件,即使不向use子句添加其他框架。

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.

选项卡开始不可见,其框架未初始化。TabA.Activate;显示选项卡并设置焦点。TabA.Frame。Label1很容易访问该框架的控件。塔巴。可见:= False;隐藏选项卡并释放框架。

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.

更新:我决定在FMX应用程序中使用表单而不是框架,因为框架在设计时不能使用样式。一个副作用是,我可以使用选项卡标题的表单标题而不是类函数。

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;

#1


0  

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.

这个解决方案对每一帧都有一个全局变量。所有框架都可以在其实现部分中使用主窗体单元,并且可以方便地访问其他框架和它们的所有控件,即使不向use子句添加其他框架。

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.

选项卡开始不可见,其框架未初始化。TabA.Activate;显示选项卡并设置焦点。TabA.Frame。Label1很容易访问该框架的控件。塔巴。可见:= False;隐藏选项卡并释放框架。

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.

更新:我决定在FMX应用程序中使用表单而不是框架,因为框架在设计时不能使用样式。一个副作用是,我可以使用选项卡标题的表单标题而不是类函数。

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;