【问题标题】:How to have each tab of a TTabControl in its own unit / use TFrame instead of tabs?如何在自己的单元中拥有 TTabControl 的每个选项卡/使用 TFrame 而不是选项卡?
【发布时间】:2018-07-31 00:35:18
【问题描述】:

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

效果很好,但我不喜欢

  • 一切都在主形里混在一起

  • 即使选项卡内容从未变得可见,也会对其进行初始化。

所以我考虑使用在标签可见时创建的框架。

每个框架只能存在一次,并且应该可以轻松地从另一个框架操作一个框架(另一个框架上的访问控制)。

我喜欢优雅的解决方案和简短的代码 :)

这是我已经找到的,非常好,但很旧: Replacing TabSheets with Frames - by Dan Miser

【问题讨论】:

    标签: delphi firemonkey tabcontrol vcl tframe


    【解决方案1】:

    此解决方案的每个帧都有一个全局变量。所有框架都可以在其实现部分使用主窗体单元,并且可以轻松访问其他框架及其所有控件,即使不将其他框架添加到 uses 子句。

    选项卡开始不可见,并且它们的框架未初始化。 TabA.Activate; 显示选项卡并设置焦点。 TabA.Frame.Label1 可以轻松访问该框架上的控件。 TabA.Visible:= False; 隐藏标签并释放框架。

    泛型在这里真的很有帮助,我喜欢它。

    非常欢迎提出改进意见...

    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;
    
    //...
    

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

    将表单嵌入到 tabitem 中有点棘手:

    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;
    

    【讨论】:

    • 不确定我会不会隐藏,因为一旦您创建了框架,就不必再次创建它。相反,我会测试是否分配了适当的 var,如果尚未创建,则创建。这意味着您只需要覆盖显示,而不是隐藏。就我个人而言,我不喜欢在实现部分使用 _FormMain。我知道有时这是必要的/务实的,特别是在维护遗留代码时,但对我来说,这通常表明设计不佳。一般来说,虽然这个想法是叠加的。
    • @Dsm,谢谢,我同意。这取决于要求,释放框架是否好,这只是一个例子。是的,直接访问其他框架组件通常不是好的设计;相反,框架之间应该有清晰的接口。在我的场景中,这次务实的方式还可以,我想分享它,因为对泛型的有用使用。别忘了,这是为了改进将所有内容放在主窗体中......
    • 我仍然会删除 uses _FormMain; 它不会对您的答案添加任何内容,并且可能会鼓励不良做法。
    • 我仍然同意这种设计不会赢得任何奖品,但添加必要的间接、接口、消息会鼓励“TabControl 中的一切”方法的前用户不要考虑它,因为它变得更加复杂(也许不可能或需要太多工作来改变现有解决方案,这就是问题的意图)。
    • 关于您删除uses _FormMain 的建议:那么您可能错过了一个关键点,为什么我这样做并使用泛型。目的是让直接访问其他框架的控件/方法变得非常容易,而无需使用类型转换,甚至不包括它们的单元。如果直接从其他框架访问框架很糟糕,那么从主窗体访问它们也很糟糕,因此将这些类型变量存在的整个概念都是错误的 - 但这是我回答的主要部分:) @Dsm跨度>
    猜你喜欢
    • 2021-03-23
    • 1970-01-01
    • 2016-06-14
    • 1970-01-01
    • 1970-01-01
    • 2021-11-12
    • 1970-01-01
    • 2019-12-27
    • 1970-01-01
    相关资源
    最近更新 更多