5

Necesito saber los fundamentos detrás de hacer un componente producir y administrar subcomponentes. Intenté esto originalmente al crear un TCollection, e intenté poner un nombre en cada TCollectionItem. Pero aprendí que no es tan fácil como esperaba.¿Cómo crear un componente con subcomponentes con nombre?

Así que ahora voy a comenzar este proyecto desde cero nuevamente, y me gustaría hacerlo bien esta vez. Estos subcomponentes no son componentes visuales, y no deberían tener ninguna pantalla o ventana, solo basados ​​en TComponent. El componente principal que contiene estos subcomponentes también se basará en TComponent. Entonces, nada aquí es visual en absoluto, y no quiero un pequeño icono en mi formulario (en tiempo de diseño) para cada uno de estos subcomponentes.

Me gustaría poder mantener y administrar estos subcomponentes de forma similar a la colección. Lo importante es que estos subcomponentes deben crearse, nombrarse y agregarse al origen del formulario, al igual que los elementos del menú, por ejemplo. Este es el objetivo principal de la idea, en primer lugar, si no pueden nombrarse, entonces toda esta idea es kaput.

Ah, otra cosa importante: el componente principal que es el padre de todos los subcomponentes debe ser capaz de guardar estos subcomponentes en el archivo DFM.

Ejemplo:

lugar de acceder a uno de estos sub elementos como:

MyForm.MyItems[1].DoSomething(); 

En vez de ello gustaría hacer algo como:

MyForm.MyItem2.DoSomething(); 

Así que no tengo confiar en conocer la ID de cada sub artículo.

EDIT:

me sentí un poco necesario incluir mi código original por lo que se puede ver cómo funciona la colección original. Aquí es sólo el elemento de la colección del lado del servidor y la recogida despojado de la unidad completa:

// Command Collections 
// Goal: Allow entering pre-set commands with unique Name and ID 
// Each command has its own event which is triggered when command is received 
// TODO: Name each collection item as a named component in owner form 

    //Determines how commands are displayed in collection editor in design-time 
    TJDCmdDisplay = (cdName, cdID, cdCaption, cdIDName, cdIDCaption); 

    TJDScktSvrCmdEvent = procedure(Sender: TObject; Socket: TJDServerClientSocket; 
    const Data: TStrings) of object; 

    TSvrCommands = class(TCollection) 
    private 
    fOwner: TPersistent; 
    fOnUnknownCommand: TJDScktSvrCmdEvent; 
    fDisplay: TJDCmdDisplay; 
    function GetItem(Index: Integer): TSvrCommand; 
    procedure SetItem(Index: Integer; Value: TSvrCommand); 
    procedure SetDisplay(const Value: TJDCmdDisplay); 
    protected 
    function GetOwner: TPersistent; override; 
    public 
    constructor Create(AOwner: TPersistent); 
    destructor Destroy; 
    procedure DoCommand(const Socket: TJDServerClientSocket; 
     const Cmd: Integer; const Data: TStrings); 
    function Add: TSvrCommand; 
    property Items[Index: Integer]: TSvrCommand read GetItem write SetItem; 
    published 
    property Display: TJDCmdDisplay read fDisplay write SetDisplay; 
    property OnUnknownCommand: TJDScktSvrCmdEvent 
     read fOnUnknownCommand write fOnUnknownCommand; 
    end; 

    TSvrCommand = class(TCollectionItem) 
    private 
    fID: Integer; 
    fOnCommand: TJDScktSvrCmdEvent; 
    fName: String; 
    fParamCount: Integer; 
    fCollection: TSvrCommands; 
    fCaption: String; 
    procedure SetID(Value: Integer); 
    procedure SetName(Value: String); 
    procedure SetCaption(const Value: String); 
    protected 
    function GetDisplayName: String; override; 
    public 
    procedure Assign(Source: TPersistent); override; 
    constructor Create(Collection: TCollection); override; 
    destructor Destroy; override; 
    published 
    property ID: Integer read fID write SetID; 
    property Name: String read fName write SetName; 
    property Caption: String read fCaption write SetCaption; 
    property ParamCount: Integer read fParamCount write fParamCount; 
    property OnCommand: TJDScktSvrCmdEvent read fOnCommand write fOnCommand; 
    end; 

//////////////////////////////////////////////////////////////////////////////// 
implementation 
//////////////////////////////////////////////////////////////////////////////// 

{ TSvrCommands } 

function TSvrCommands.Add: TSvrCommand; 
begin 
    Result:= inherited Add as TSvrCommand; 
end; 

constructor TSvrCommands.Create(AOwner: TPersistent); 
begin 
    inherited Create(TSvrCommand); 
    Self.fOwner:= AOwner; 
end; 

destructor TSvrCommands.Destroy; 
begin 
    inherited Destroy; 
end; 

procedure TSvrCommands.DoCommand(const Socket: TJDServerClientSocket; 
    const Cmd: Integer; const Data: TStrings); 
var 
    X: Integer; 
    C: TSvrCommand; 
    F: Bool; 
begin 
    F:= False; 
    for X:= 0 to Self.Count - 1 do begin 
    C:= GetItem(X); 
    if C.ID = Cmd then begin 
     F:= True; 
     try 
     if assigned(C.fOnCommand) then 
      C.fOnCommand(Self, Socket, Data); 
     except 
     on e: exception do begin 
      raise Exception.Create(
      'Failed to execute command '+IntToStr(Cmd)+': '+#10+e.Message); 
     end; 
     end; 
     Break; 
    end; 
    end; 
    if not F then begin 
    //Command not found 

    end; 
end; 

function TSvrCommands.GetItem(Index: Integer): TSvrCommand; 
begin 
    Result:= TSvrCommand(inherited GetItem(Index)); 
end; 

function TSvrCommands.GetOwner: TPersistent; 
begin 
    Result:= fOwner; 
end; 

procedure TSvrCommands.SetDisplay(const Value: TJDCmdDisplay); 
begin 
    fDisplay := Value; 
end; 

procedure TSvrCommands.SetItem(Index: Integer; Value: TSvrCommand); 
begin 
    inherited SetItem(Index, Value); 
end; 

{ TSvrCommand } 

procedure TSvrCommand.Assign(Source: TPersistent); 
begin 
    inherited; 

end; 

constructor TSvrCommand.Create(Collection: TCollection); 
begin 
    inherited Create(Collection); 
    fCollection:= TSvrCommands(Collection); 
end; 

destructor TSvrCommand.Destroy; 
begin 
    inherited Destroy; 
end; 

function TSvrCommand.GetDisplayName: String; 
begin   
    case Self.fCollection.fDisplay of 
    cdName: begin 
     Result:= fName; 
    end; 
    cdID: begin 
     Result:= '['+IntToStr(fID)+']'; 
    end; 
    cdCaption: begin 
     Result:= fCaption; 
    end; 
    cdIDName: begin 
     Result:= '['+IntToStr(fID)+'] '+fName; 
    end; 
    cdIDCaption: begin 
     Result:= '['+IntToStr(fID)+'] '+fCaption; 
    end; 
    end; 
end; 

procedure TSvrCommand.SetCaption(const Value: String); 
begin 
    fCaption := Value; 
end; 

procedure TSvrCommand.SetID(Value: Integer); 
begin 
    fID:= Value; 
end; 

procedure TSvrCommand.SetName(Value: String); 
begin 
    fName:= Value; 
end; 
+0

Podría estudiar el código fuente de las tres "cosas existentes similares" que citó. ¿Cómo implementaron esto los desarrolladores de Delphi? –

+0

Ahora después de su edición, queda claro qué es exactamente lo que desea: No, esto no es posible. Pero dudo que este sea realmente tu deseo. ¿Por qué quieres abordar el elemento de la colección por nombre en el código? – NGLN

+0

Quizás no sea el elemento de la colección real al que se hace referencia desde el código, sino que crea algún componente invisible detrás de cada elemento de la colección que tiene un nombre que se guardará en el DFM. Sin embargo, como se menciona en otro comentario a continuación en su respuesta, no estoy seguro de cómo mostrar las propiedades de ese componente en el inspector de objetos cuando ya se muestran las propiedades de TCollectionItem? –

Respuesta

8

This Thread me ayudó a crear algo como lo discutimos ayer. Tomé el paquete publicado allí y lo modifiqué un poco. Aquí está la fuente:

TestComponents.pas

unit TestComponents; 

interface 

uses 
    Classes; 

type 
    TParentComponent = class; 

    TChildComponent = class(TComponent) 
    private 
    FParent: TParentComponent; 
    procedure SetParent(const Value: TParentComponent); 
    protected 
    procedure SetParentComponent(AParent: TComponent); override; 
    public 
    destructor Destroy; override; 
    function GetParentComponent: TComponent; override; 
    function HasParent: Boolean; override; 
    property Parent: TParentComponent read FParent write SetParent; 
    end; 

    TParentComponent = class(TComponent) 
    private 
    FChilds: TList; 
    protected 
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override; 
    public 
    constructor Create(AOwner: TComponent); override; 
    destructor Destroy; override; 
    property Childs: TList read FChilds; 
    end; 

implementation 

{ TChildComponent } 

destructor TChildComponent.Destroy; 
begin 
    Parent := nil; 
    inherited; 
end; 

function TChildComponent.GetParentComponent: TComponent; 
begin 
    Result := FParent; 
end; 

function TChildComponent.HasParent: Boolean; 
begin 
    Result := Assigned(FParent); 
end; 

procedure TChildComponent.SetParent(const Value: TParentComponent); 
begin 
    if FParent <> Value then 
    begin 
    if Assigned(FParent) then 
     FParent.FChilds.Remove(Self); 
    FParent := Value; 
    if Assigned(FParent) then 
     FParent.FChilds.Add(Self); 
    end; 
end; 

procedure TChildComponent.SetParentComponent(AParent: TComponent); 
begin 
    if AParent is TParentComponent then 
    SetParent(AParent as TParentComponent); 
end; 

{ TParentComponent } 

constructor TParentComponent.Create(AOwner: TComponent); 
begin 
    inherited; 
    FChilds := TList.Create; 
end; 

destructor TParentComponent.Destroy; 
var 
    I: Integer; 
begin 
    for I := 0 to FChilds.Count - 1 do 
    FChilds[0].Free; 
    FChilds.Free; 
    inherited; 
end; 

procedure TParentComponent.GetChildren(Proc: TGetChildProc; Root: TComponent); 
var 
    i: Integer; 
begin 
    for i := 0 to FChilds.Count - 1 do 
    Proc(TComponent(FChilds[i])); 
end; 

end. 

TestComponentsReg.pas

unit TestComponentsReg; 

interface 

uses 
    Classes, 
    DesignEditors, 
    DesignIntf, 
    TestComponents; 

type 
    TParentComponentEditor = class(TComponentEditor) 
    procedure ExecuteVerb(Index: Integer); override; 
    function GetVerb(Index: Integer): string; override; 
    function GetVerbCount: Integer; override; 
    end; 

procedure Register; 

implementation 

uses 
    ColnEdit; 

type 
    TChildComponentCollectionItem = class(TCollectionItem) 
    private 
    FChildComponent: TChildComponent; 
    function GetName: string; 
    procedure SetName(const Value: string); 
    protected 
    property ChildComponent: TChildComponent read FChildComponent write FChildComponent; 
    function GetDisplayName: string; override; 
    public 
    constructor Create(Collection: TCollection); override; 
    destructor Destroy; override; 
    published 
    property Name: string read GetName write SetName; 
    end; 

    TChildComponentCollection = class(TOwnedCollection) 
    private 
    FDesigner: IDesigner; 
    public 
    property Designer: IDesigner read FDesigner write FDesigner; 
    end; 

procedure Register; 
begin 
    RegisterClass(TChildComponent); 
    RegisterNoIcon([TChildComponent]); 
    RegisterComponents('Test', [TParentComponent]); 
    RegisterComponentEditor(TParentComponent, TParentComponentEditor); 
end; 

{ TParentComponentEditor } 

procedure TParentComponentEditor.ExecuteVerb(Index: Integer); 
var 
    LCollection: TChildComponentCollection; 
    i: Integer; 
begin 
    LCollection := TChildComponentCollection.Create(Component, TChildComponentCollectionItem); 
    LCollection.Designer := Designer; 
    for i := 0 to TParentComponent(Component).Childs.Count - 1 do 
    with TChildComponentCollectionItem.Create(nil) do 
    begin 
     ChildComponent := TChildComponent(TParentComponent(Component).Childs[i]); 
     Collection := LCollection; 
    end; 
    ShowCollectionEditorClass(Designer, TCollectionEditor, Component, LCollection, 'Childs'); 
end; 

function TParentComponentEditor.GetVerb(Index: Integer): string; 
begin 
    Result := 'Edit Childs...'; 
end; 

function TParentComponentEditor.GetVerbCount: Integer; 
begin 
    Result := 1; 
end; 

{ TChildComponentCollectionItem } 

constructor TChildComponentCollectionItem.Create(Collection: TCollection); 
begin 
    inherited; 
    if Assigned(Collection) then 
    begin 
    FChildComponent := TChildComponent.Create(TComponent(TOwnedCollection(Collection).Owner).Owner); 
    FChildComponent.Name := TChildComponentCollection(Collection).Designer.UniqueName(TChildComponent.ClassName); 
    FChildComponent.Parent := TParentComponent(TComponent(TOwnedCollection(Collection).Owner)); 
    end; 
end; 

destructor TChildComponentCollectionItem.Destroy; 
begin 
    FChildComponent.Free; 
    inherited; 
end; 

function TChildComponentCollectionItem.GetDisplayName: string; 
begin 
    Result := FChildComponent.Name; 
end; 

function TChildComponentCollectionItem.GetName: string; 
begin 
    Result := FChildComponent.Name; 
end; 

procedure TChildComponentCollectionItem.SetName(const Value: string); 
begin 
    FChildComponent.Name := Value; 
end; 

end. 

Lo más importante es la RegisterNoIcon que evita que muestra el componente en el formulario cuando se crea. Los métodos reemplazados en TChildComponent están causando que se aniden dentro de TParentComponent.

Editar: He añadido una colección temporal para editar los elementos en el TCollectionEditor incorporado en lugar de tener que escribir uno propio. La única desventaja es que TChildComponentCollectionItem tiene que publicar todas las propiedades que TChildComponent ha publicado para poder editarlas dentro de la OI.

+2

+1 Muy agradable. Sospechaba la necesidad de usar 'Designer.CreateComponent' para agregar el elemento al archivo fuente, pero' Designer.Modified' se ocupa bastante bien. ¡Buen descubrimiento! – NGLN

+0

@Jerry Agregue 'DesignIDE' a la sección de requisitos del archivo fuente de su paquete. Ver [¿Qué pasó con Proxies.pas?] (Http://edn.embarcadero.com/article/27717). – NGLN

+0

¡ES HERMOSO! Les debo demasiado a los dos. –

1

Implementar TCollectionItem.GetDisplayName a "nombre" los elementos de la colección.

Y en relación con la colección: cuando se trata de una propiedad publicada, la colección se nombrará automáticamente como el nombre de la propiedad.

Tenga cuidado al implement GetOwner cuando crea propiedades de TPersistent.

+0

No, no me refiero a 'Mostrar nombre'. Estoy más que consciente de eso. Necesito crear otro componente invisible para representar cada elemento, un componente nombrado. –

+2

¿Cuál es la razón por la que debe heredar de TComponent? –

+0

Puedo heredarlo de lo que sea, siempre que no sea nada visual, y puede tener un nombre en la clase del formulario. Supongo que los componentes son los más adecuados, pero corrígeme si me equivoco. –

2

utilizar la rutina TComponent.SetSubComponent:

type 
    TComponent1 = class(TComponent) 
    private 
    FSubComponent: TComponent; 
    procedure SetSubComponent(Value: TComponent); 
    public 
    constructor Create(AOwner: TComponent); override; 
    published 
    property SubComponent: TComponent read FSubComponent write SetSubComponent; 
    end; 

procedure Register; 

implementation 

procedure Register; 
begin 
    RegisterComponents('Samples', [TComponent1]); 
end; 

{ TComponent1 } 

constructor TComponent1.Create(AOwner: TComponent); 
begin 
    inherited Create(AOwner); 
    FSubComponent := TComponent.Create(Self); // Nót AOwner as owner here !! 
    FSubComponent.Name := 'MyName'; 
    FSubComponent.SetSubComponent(True); 
end; 

procedure TComponent1.SetSubComponent(Value: TComponent); 
begin 
    FSubComponent.Assign(Value); 
end; 

ahora entiendo este componente sub sería parte de un elemento de la colección. En ese caso: no hay diferencia, use este método.

+0

Eso podría funcionar, en una escala mucho mayor, por supuesto. El ejemplo es el uso de un solo subcomponente sin una colección, pero probablemente funcionaría utilizándolo desde un TCollectionItem. ¿Pero esto realmente creará un nuevo componente, guardado dentro de la clase del formulario? –

+0

Sí. Una cita de mi enlace: _A menos que un componente así llame a SetSubComponent con IsSubComponent establecido en True, sus propiedades publicadas no se guardarán en el archivo de formulario._ – NGLN

+0

En realidad, no veo cómo puedo hacer las propiedades de este subcomponente visible en el Inspector de objetos porque lo que realmente se seleccionó para mostrar en el Inspector de objetos sería el elemento en el editor de colecciones (TCollectionItem) –

Cuestiones relacionadas