2012-05-19 2 views
4

Я создаю пользовательский элемент управления 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 каждому отдельному элементу в его конструкторе, и каждый элемент регистрирует себя в списке элементов. В этом случае список элементов не будет иметь никакой процедуры добавления, и мне, вероятно, даже не понадобится отдельный объект списка. Я просто уверен, что в этом должен быть какой-то большой трюк, который мне не хватает, и я открыт для любых масштабных изменений в структуре, чтобы более эффективно адаптироваться к этому.

+0

PS - Прибегая к библиотеке третьей стороной не может быть ответа, так как этот проект на самом деле имел в виду для меня изучите аспекты Delphi на более низком уровне. Поэтому такие вещи, как GLScene, которые у меня уже есть, не будут ответом. –

ответ

5

Это классическое использование polymorphism. Согласно XE2 documentation (C++, но здесь применимо):

Полиморфные классы: Классы, которые обеспечивают одинаковый интерфейс, но могут быть реализованы для обслуживания различных конкретных требований, называются полиморфными классами. Класс является полиморфным, если он объявляет или наследует хотя бы одну виртуальную (или чистую виртуальную) функцию.

Вот пример, который выполняет именно то, что вы хотите сделать. Он создает базовый тип (TBase) с абстрактным виртуальным методом (Draw), который должен реализовать каждый потомок, и два отдельных типа потомков (TChildOne и TChildTwo), каждый из которых реализует собственный метод Draw.

Объявлен массив из 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. 

Выход в окно консоли выглядит следующим образом:

enter image description here

+0

+1 и принято - и я должен был подумать об этом раньше, это как раз то, как работает «TThread» –