【问题标题】:How to cast with a class reference containing the type of some frame (Delphi)如何使用包含某些框架类型的类引用进行强制转换(Delphi)
【发布时间】:2020-09-17 00:44:44
【问题描述】:

我有一个在许多表上实现 CRUD 的应用程序。

主表单为每个表格提供一个选项卡,以及一个工具栏,其中包含对所有选项卡都有效的插入、更新、删除按钮。

每次更改选项卡时,都会有一个变量 frameClass: TFrameClass;(其中TFrameClass = class of TFrame)获取选项卡下创建的框架类型,其他变量frame: TFrame;获取选项卡下创建的框架。

当点击 INSERT 按钮时,我想指向与活动选项卡对应的Insert() 过程,例如:

frameClass(frame).insert    // trying to cast

但是编译器说插入不是一个有效的方法。但是,如果我使用 frameClass 的内容进行投射,它就可以工作:

TFrame1(frame).insert;    // does not work in general case.

我做错了什么?

这是示例代码:

Unit1.pas

unit unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Menus,
  FMX.TabControl,
  unit2, unit3;

type
  TFrameClass = class of TFrame;

  TFormMain = class(TForm)
    TabControl1: TTabControl;
    TabItem1: TTabItem;
    TabItem2: TTabItem;
    PopupMenu1: TPopupMenu;
    MenuItemInsert: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure TabControl1Change(Sender: TObject);
    procedure MenuItemInsertClick(Sender: TObject);
  public
    frame: TFrame;
    frameClass: TFrameClass;
    frames: array[0..1] of TFrameClass;
  end;

var
  FormMain: TFormMain;

implementation

{$R *.fmx}

procedure TFormMain.FormCreate(Sender: TObject);
begin
  frames[0]:= TFrame1;
  frames[1]:= TFrame2;
end;

procedure TFormMain.MenuItemInsertClick(Sender: TObject);
begin
  // want the insert click to work whatever the activeTab is
  // (frame as FrameClass).insert;   // insert is not a method
  // THIS IS THE GIST OF MY QUESTION:
  // TFrame1(frame).insert;          // it works but want it general
  // FrameClass(frame).insert;       // this is how I'd like it to work
end;

procedure TFormMain.TabControl1Change(Sender: TObject);
begin
  frameClass:= frames[tabControl1.tabIndex];
  frame:= frameClass.Create(tabControl1.activeTab);
  frame.Parent:= tabControl1.activeTab;end;    
end.

Unit2.pas

unit Unit2;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 
  FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
  FMX.Controls.Presentation;

type
// if try to descend from other than TFrame, some properties like align, size, etc, are lost
  TFrame1 = class(TFrame)
    Label1: TLabel;
  private
    { Private declarations }
  public
    { Public declarations }
    procedure insert;
  end;

implementation

{$R *.fmx}

procedure TFrame1.insert;
begin
  //
end;

end.

unit3.pas

unit Unit3;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 
  FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
  FMX.Controls.Presentation, FMX.Edit;

type
  // if try to descend from other than TFrame, some properties like align, size, etc, are lost
  TFrame2 = class(TFrame)
    Edit1: TEdit;
  private
    { Private declarations }
  public
    { Public declarations }
    procedure insert;
  end;

implementation

{$R *.fmx}

procedure TFrame2.insert;
begin
  //
end;

end.

【问题讨论】:

    标签: delphi delphi-10.2-tokyo


    【解决方案1】:

    它不能按您想要的方式工作,因为基类TFrame 没有您要查找的方法,只有您的派生框架类有。当您通过TFrameClass 类引用(或基TFrame 对象指针)访问TFrame 对象时,您只能访问TFrame 本身中的方法。要访问派生类方法,您需要执行以下操作:

    if frame is TFrame1 then
      TFrame1(frame).insert  
    else if frame is TFrame2 then
      TFrame2(frame).insert;
    

    这会挫败你想要完成的事情。为此,您需要让您的框架类派生自一个共同的祖先,该祖先声明您想要的方法,然后您可以在需要时通过该祖先访问这些方法。

    有两种方法可以做到这一点:

    1. 创建一个从 TFrame 派生的新基类并具有您想要的方法,然后更改您的框架类以从该基类派生。

    Unit1.pas

    unit Unit1;
    
    interface
    
    uses
      System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
      FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Menus,
      FMX.TabControl,
      MyFrameBase;
    
    type
      TMyFrameBaseClass = class of TMyFrameBase;
    
      TFormMain = class(TForm)
        TabControl1: TTabControl;
        TabItem1: TTabItem;
        TabItem2: TTabItem;
        PopupMenu1: TPopupMenu;
        MenuItemInsert: TMenuItem;
        procedure FormCreate(Sender: TObject);
        procedure TabControl1Change(Sender: TObject);
        procedure MenuItemInsertClick(Sender: TObject);
      public
        frame: TMyFrameBase;
        frames: array[0..1] of TMyFrameBaseClass;
      end;
    
    var
      FormMain: TFormMain;
    
    implementation
    
    {$R *.fmx}
    
    uses
      Unit2, Unit3;
    
    procedure TFormMain.FormCreate(Sender: TObject);
    begin
      frames[0] := TFrame1;
      frames[1] := TFrame2;
    end;
    
    procedure TFormMain.MenuItemInsertClick(Sender: TObject);
    begin
      frame.Insert;
    end;
    
    procedure TFormMain.TabControl1Change(Sender: TObject);
    var
      frameClass: TMyFrameBaseClass;
    begin
      frameClass := frames[TabControl1.TabIndex];
      frame := frameClass.Create(TabControl1.ActiveTab);
      frame.Parent := TabControl1.ActiveTab;
    end;    
    
    end.
    

    MyFrameBase.pas

    unit MyFrameBase;
    
    interface
    
    uses
      FMX.Forms;
    
    type
      TMyFrameBase = class(TFrame)
      public
        procedure Insert; virtual; abstract;
      end;
    
    implementation
    
    end.
    

    Unit2.pas

    unit Unit2;
    
    interface
    
    uses
      System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 
      FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
      FMX.Controls.Presentation,
      MyFrameBase;
    
    type
      TFrame1 = class(TMyFrameBase)
        Label1: TLabel;
      private
        { Private declarations }
      public
        { Public declarations }
        procedure Insert; override;
      end;
    
    implementation
    
    {$R *.fmx}
    
    procedure TFrame1.Insert;
    begin
      //
    end;
    
    end.
    

    Unit3.pas

    unit Unit3;
    
    interface
    
    uses
      System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 
      FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
      FMX.Controls.Presentation, FMX.Edit,
      MyFrameBase;
    
    type
      TFrame2 = class(TMyFrameBase)
        Edit1: TEdit;
      private
        { Private declarations }
      public
        { Public declarations }
        procedure Insert; override;
      end;
    
    implementation
    
    {$R *.fmx}
    
    procedure TFrame2.Insert;
    begin
      //
    end;
    
    end.
    
    1. 声明具有您所需方法的interface,然后让您的框架类实现该接口。您可以在需要时为该接口查询框架对象。

    Unit1.pas

    unit Unit1;
    
    interface
    
    uses
      System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
      FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Menus,
      FMX.TabControl;
    
    type
      TFrameClass = class of TFrame;
    
      TFormMain = class(TForm)
        TabControl1: TTabControl;
        TabItem1: TTabItem;
        TabItem2: TTabItem;
        PopupMenu1: TPopupMenu;
        MenuItemInsert: TMenuItem;
        procedure FormCreate(Sender: TObject);
        procedure TabControl1Change(Sender: TObject);
        procedure MenuItemInsertClick(Sender: TObject);
      public
        frame: TFrame;
        frames: array[0..1] of TFrameClass;
      end;
    
    var
      FormMain: TFormMain;
    
    implementation
    
    {$R *.fmx}
    
    uses
      MyFrameIntf, Unit2, Unit3;
    
    procedure TFormMain.FormCreate(Sender: TObject);
    begin
      frames[0] := TFrame1;
      frames[1] := TFrame2;
    end;
    
    procedure TFormMain.MenuItemInsertClick(Sender: TObject);
    var
      intf: IMyFrameIntf;
    begin
      if Supports(frame, IMyFrameIntf, intf) then
        intf.Insert;
    end;
    
    procedure TFormMain.TabControl1Change(Sender: TObject);
    var
      frameClass: TFrameClass;
    begin
      frameClass := frames[TabControl1.TabIndex];
      frame := frameClass.Create(TabControl1.ActiveTab);
      frame.Parent := TabControl1.ActiveTab;
    end;    
    
    end.
    

    MyFrameIntf.pas

    unit MyFrameIntf;
    
    interface
    
    type
      IMyFrameIntf = interface
        ['{83A4D2BF-C72F-4075-9450-4A1480A674A4}']
        procedure Insert;
      end;
    
    implementation
    
    end.
    

    Unit2.pas

    unit Unit2;
    
    interface
    
    uses
      System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 
      FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
      FMX.Controls.Presentation,
      MyFrameIntf;
    
    type
      TFrame1 = class(TFrame, IMyFrameIntf)
        Label1: TLabel;
      private
        { Private declarations }
      public
        { Public declarations }
        procedure Insert;
      end;
    
    implementation
    
    {$R *.fmx}
    
    procedure TFrame1.Insert;
    begin
      //
    end;
    
    end.
    

    Unit3.pas

    unit Unit3;
    
    interface
    
    uses
      System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 
      FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
      FMX.Controls.Presentation, FMX.Edit,
      MyFrameBase;
    
    type
      TFrame2 = class(TFrame, IMyFrameIntf)
        Edit1: TEdit;
      private
        { Private declarations }
      public
        { Public declarations }
        procedure Insert;
      end;
    
    implementation
    
    {$R *.fmx}
    
    procedure TFrame2.Insert;
    begin
      //
    end;
    
    end.
    

    【讨论】:

    • 非常感谢雷米!备选方案 1 对我不起作用。每次我尝试“TFrame1 = class(不同于 TFrame)”时,当框架关闭并重新打开时,框架会丢失几个属性,如对齐、大小等。我不知道我的 10.2 Delphi 中是否存在错误。
    • 这听起来绝对是一个你应该report to Embarcadero 的错误,因为继承不应该丢失这样的属性,所以我怀疑这将是 IDE 本身的错误,或者生成的 RTTI (众所周知,这会困扰最近的版本)。
    • 不过,使用类引用进行转换会很好,因此不需要接口或` 如果 frame 是 TFrame1 则 TFrame1(frame).insert 否则如果 frame 是 TFrame2 则 TFrame2(frame ).插入;`。我假设 frame 指向在活动选项卡中创建的框架,然后用 frameClass 投射告诉布局解释它,这有什么问题?
    • "使用类引用进行转换会很好......我的假设有什么问题......使用 frameClass 进行转换告诉布局来解释它?" - 抱歉,但这根本无法以这种方式解决,这不是类引用的工作方式。转换需要在编译时知道,而不是在运行时。您可以使用类引用来创建对象,但不能强制转换对象。
    【解决方案2】:

    像现在一样,从 New Items 对话框、Delphi Projects|Delphi File|VCL Frame 创建您的基类(带有您想要的附加功能)。 但是,然后通过从新项目对话框 Delphi Project|Inheritable Items|您的基本对话框(将已添加到此页面)中选择,从您创建的基础框架开发所有其他框架。 请注意,这与 Remy 的第一个解决方案并没有什么不同,但它解释了您在实践中可能会如何做到这一点。显然你在尝试遵循 Remy 的指示时做错了。

    【讨论】:

      猜你喜欢
      • 2019-05-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-09
      • 1970-01-01
      • 1970-01-01
      • 2019-02-15
      相关资源
      最近更新 更多