5

我正在构建一个自定义 OpenGL 控件,它包含一个项目列表,其中每个项目可能是一个不同的类,但继承自某个公共类类型。我不知道如何以一种可以循环遍历这些项目并执行某些操作的方式来执行此操作,这些操作预计将在继承的类中被覆盖。

更具体地说,这是要绘制到画布上的视觉对象列表。我有一个通用类TGLItem,用于创建许多继承类。例如,TGLCar继承自TGLItem并添加到列表中TGLItems。此自定义列表类是控件的一部分。

当控件绘制时,它会循环遍历这个项目列表并调用Draw每个项目的过程。Draw旨在被实际绘制项目的继承类覆盖。所以真正的工作是从类Draw中实现的过程中完成的,TGLCar但它只能从主控件中调用。

主控件 ( TGLImage) 不知道实际继承的项目是什么,但能够调用它的Draw过程,期望它绘制到 OpenGL。

如何以适应这种情况的方式构建此项目列表和项目库?这是我到目前为止所拥有的:

  TGLItems = class(TPersistent)
  private
    FItems: TList;
    function GetItem(Index: Integer): TGLItem;
    procedure SetItem(Index: Integer; const Value: TGLItem);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Add(AItem: TGLItem);
    function Count: Integer;
    property Items[Index: Integer]: TGLItem read GetItem write SetItem; default;
  end;

  TGLItem = class(TPersistent)
  private
    FPosition: TGLPosition;
    FDimensions: TGLDimensions;
    FOwner: TGLItems;
    FItemClass: TGLItemClass;
    procedure PositionChanged(Sender: TObject);
    procedure DimensionsChanged(Sender: TObject);
    procedure SetPosition(const Value: TGLPosition);
    procedure SetDimensions(const Value: TGLDimensions);
  public
    constructor Create(Owner: TGLItems);
    destructor Destroy; override;
    procedure Draw;
    property Owner: TGLItems read FOwner;
    property ItemClass: TGLItemClass read FItemClass;
  published
    property Position: TGLPosition read FPosition write SetPosition;
    property Dimensions: TGLDimensions read FDimensions write SetDimensions;
  end;

执行...

{ TGLItem }

constructor TGLItem.Create;
begin
  FPosition:= TGLPosition.Create;
  FPosition.OnChange:= PositionChanged;
  FDimensions:= TGLDimensions.Create;
  FDimensions.OnChange:= DimensionsChanged;
end;

destructor TGLItem.Destroy;
begin
  FPosition.Free;
  FDimensions.Free;
  inherited;
end;

procedure TGLItem.DimensionsChanged(Sender: TObject);
begin

end;

procedure TGLItem.Draw;
begin
  //Draw to gl scene

end;

procedure TGLItem.PositionChanged(Sender: TObject);
begin

end;

procedure TGLItem.SetDimensions(const Value: TGLDimensions);
begin
  FDimensions.Assign(Value);
end;

procedure TGLItem.SetPosition(const Value: TGLPosition);
begin
  FPosition.Assign(Value);
end;

{ TGLItems }

procedure TGLItems.Add(AItem: TGLItem);
begin
  FItems.Add(AItem);
  //Expects objects to be created and maintained elsewhere
  //This list object will not create/destroy any items
end;

function TGLItems.Count: Integer;
begin
  Result:= FItems.Count;
end;

constructor TGLItems.Create;
begin
  FItems:= TList.Create;
end;

destructor TGLItems.Destroy;
begin
  FItems.Free;
  inherited;
end;

function TGLItems.GetItem(Index: Integer): TGLItem;
begin
  Result:= TGLItem(FItems[Index]);
end;

procedure TGLItems.SetItem(Index: Integer; const Value: TGLItem);
begin
  TGLItem(FItems[Index]).Assign(Value);
end;

它的 OpenGL 部分在这种情况下不一定相关,我只是想解释一下它的目的是什么,以便了解我期望它如何工作。

我也有将TGLItems列表对象传递给其构造函数中的每个单独项目的想法,并让每个项目在项目列表中注册其自身。在这种情况下,项目列表将没有任何添加过程,我什至可能不需要单独的列表对象。我只是确定应该有一些我缺少的大技巧,并且我愿意对结构进行任何大规模更改以更有效地适应这一点。

4

1 回答 1

5

这是多态性的经典用法。根据XE2 文档(C++,但适用于此处):

多态类:提供相同接口但可以实现以满足不同特定需求的类称为多态类。如果一个类声明或继承了至少一个虚拟(或纯虚拟)函数,则它是多态的。

这是一个完全完成您想要做的事情的例子。它创建了一个基类型 ( ),其中包含每个后代必须实现TBase的抽象虚拟方法 ( ),以及两个独立的后代类型 ( and ),每个都实现自己的方法。DrawTChildOneTChildTwoDraw

声明了一个数组TBase,有 10 个项目(见NumChildren常量)和SetLength(BaseArray, NumChildren)行。遍历数组,如果当前索引为奇数,则创建一个子类型实例;如果是偶数,则创建另一个子类型。

然后再次反向迭代数组,并TBase.Draw调用泛型。该代码根据Draw调用的类类型输出不同的行前缀。请注意,对每个数组项的Draw调用只是调用TBase.Draw(不检查该索引处的数组中的类型),但是Draw根据在数组中找到的类型,调用不同类型的特定方法那个索引。

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils;  // XE2: uses System.SysUtils;

type
  TBase = class(TObject)
    procedure Draw(const Msg: string); virtual; abstract;
  end;

  TChildOne = class(TBase)
    procedure Draw(const Msg: string); override;
  end;

  TChildTwo = class(TBase)
    procedure Draw(const Msg: string); override;
  end;

  TBaseArray = array of TBase;

procedure TChildOne.Draw(const Msg: string);
begin
  // Hard-coded for clarity. Change to something like this
  // to see without hard-coded name
  // WriteLn(Self.ClassName + '.Draw: ', Msg);
  Writeln('Type TChildOne.Draw: ', Msg);
end;

procedure TChildTwo.Draw(const Msg: string);
begin
  // Note missing 'T' before class type to make more apparent.
  // See note in TChildOne.Draw about removing hard-coded classname
  WriteLn('Type ChildTwo.Draw: ', Msg);
end;

var
  BaseArray: TBaseArray;
  i: Integer;

const
  NumChildren = 10;

begin
  SetLength(BaseArray, NumChildren);

  for i := 0 to NumChildren - 1 do
  begin
    if Odd(i) then
      BaseArray[i] := TChildOne.Create
    else
      BaseArray[i] := TChildTwo.Create;
  end;

  for i := NumChildren - 1 downto 0 do
    BaseArray[i].Draw('This is index ' + IntToStr(i));
  Readln;

end.

控制台窗口的输出如下所示:

在此处输入图像描述

于 2012-05-20T01:21:38.970 回答