Een component maken met benoemde subcomponenten?

Ik moet de basis kennen achter het maken van een component subcomponenten produceren en beheren. Ik probeerde dit oorspronkelijk door een TCollection te maken en probeerde een naam op elke TCollectionItem te zetten. Maar ik heb geleerd dat het niet zo eenvoudig is als ik had gehoopt.

Dus nu ga ik dit project helemaal opnieuw beginnen, en ik zou het deze keer graag goed willen doen. Deze subcomponenten zijn geen visuele componenten en zouden geen weergave of venster moeten hebben, gewoon gebaseerd op TComponent . Het hoofdonderdeel dat deze subcomponenten vasthoudt, is ook gebaseerd op TComponent . Niets is hier dus visueel en ik wil geen klein pictogram op mijn formulier (in ontwerptijd) voor elk van deze subcomponenten.

Ik zou graag deze subcomponenten op een collectie-achtige manier willen kunnen onderhouden en beheren. Het belangrijkste is dat deze subcomponenten moeten worden gemaakt, benoemd en toegevoegd aan de formulierbron, net zoals menu-items bijvoorbeeld zijn. Dit is het hele punt van het idee in de eerste plaats, als ze niet kunnen worden genoemd, dan is dit hele idee kapot.

Oh, nog een ander belangrijk punt: de hoofdcomponent die de ouder van alle subcomponenten is, moet deze subcomponenten in het DFM-bestand kunnen opslaan.

Voorbeeld:

In plaats van toegang tot een van deze subitems zoals:

MyForm.MyItems[1].DoSomething();

Ik zou in plaats daarvan graag iets doen als:

MyForm.MyItem2.DoSomething();

Dus ik hoef niet te vertrouwen op het kennen van de ID van elk subitem.

EDIT:

Ik vond het een beetje nodig om mijn originele code op te nemen, zodat kan worden gezien hoe de originele collectie werkt. Hier is alleen het verzameling- en verzamelingitems aan de zijde van de server ontdaan van de volledige eenheid:

//  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;
5
Je zou de broncode van de drie "gelijksoortige bestaande dingen" die je citeerde kunnen bestuderen. Hoe hebben de Delphi-ontwikkelaars dit geïmplementeerd?
toegevoegd de auteur Greg Bishop, de bron
@NGLN Bedankt voor de bewerking, trouwens: D
toegevoegd de auteur Jerry Dodge, de bron
Eén probleem dat ik me net realiseerde - stel dat ik dit ding aan het werk krijg (zoals besproken aan het einde van de chatsessie) en elk TCollectionItem heeft een van mijn 'onzichtbare benoemde componenten' erachter. Stel vervolgens dat de gebruiker om welke reden dan ook de declaratie van dit subonderdeel uit de klasse van het formulier verwijdert. Dan heeft mijn collectie-item niets meer om naar te verwijzen. Hoe om te gaan met het collectie-item in dit geval? Verwijder het? De subcomponent opnieuw maken? Een fout maken? (dit is gewoon een notitie om te weten wat je morgen moet bespreken als deze vraag in de chat wordt teruggehaald)
toegevoegd de auteur Jerry Dodge, de bron
@NGLN Dat is logisch en zou kunnen werken, maar ik zou het vreselijk vinden als u eerst op [+] moet klikken om alles te openen. Omdat elk verzamelitem en onderdeel toch met elkaar corresponderen, kan ik het onderdeel ook verpakken met behulp van het verzamelitem. Lezen/schrijven van de eigenschappen van het collectie-item zal worden doorgestuurd naar de eigenschappen van het onderdeel dat het item vertegenwoordigt. Probleem opgelost. Nu moet ik gewoon op Delphi stappen en ervoor zorgen dat dit werkt ...
toegevoegd de auteur Jerry Dodge, de bron
Dat lijkt mijn probleem te zijn op deze website, ik ben nooit naar een school gegaan om te programmeren, dus ik ken niet veel termen, maar iedereen hier verwacht dat je alles weet.
toegevoegd de auteur Jerry Dodge, de bron
En ook om te verduidelijken zoals ik niet vermeldde (hoewel irrelevant voor de vraag) - De reden hiervoor is dat ik een oud paar aangepaste Server/Client-sockets heb. Ze werken met behulp van een 'Commandonummer' met parameters heen en weer. Elke zijde heeft een vooraf gedefinieerde lijst met opdrachten in een verzameling (TSvrCommand [s] en TCliCommand [s]) - Elke opdracht die ik liever noem als MyCommand.Send (['abc', '123']) ; in plaats van MySocket.Commands [765] .Send (['abc', '123']);
toegevoegd de auteur Jerry Dodge, de bron
Ik heb het gevoel dat ik een redacteur moet maken, of in ieder geval dezelfde property-editor moet gebruiken als de TPopupMenu.
toegevoegd de auteur Jerry Dodge, de bron
Misschien wordt niet naar het daadwerkelijke verzamelitem verwezen vanuit de code, maar wordt er een onzichtbaar onderdeel gemaakt achter elk verzamelitem dat een naam heeft die moet worden opgeslagen in de DFM. Echter, zoals vermeld in een andere reactie hieronder op uw antwoord, weet ik niet zeker hoe ik de eigenschappen van die component in het objectinspectoren moet laten zien wanneer het al de eigenschappen van het TCollectionItem vertoont?
toegevoegd de auteur Jerry Dodge, de bron
Nu na uw bewerking is duidelijk wat u precies wilt: Nee, dit is niet mogelijk. Maar ik betwijfel of dit echt jouw wens is. Waarom wil je het verzamelitem op naam in code adresseren?
toegevoegd de auteur NGLN, de bron
Nee, ik denk niet dat je een huisredacteur nodig hebt. Als u een eigenschap TCollection maakt, dubbelklikt u op die eigenschap om de standaardcollectie-editor in de ontwerper te openen. Wanneer u vervolgens een verzamelitem in die verzameleditor (een apart venster) selecteert, wordt dat verzamelitem met al zijn eigenschappen weergegeven in het objectinspector. Uw subcomponent is nu een van deze eigenschappen. Open het met [+], ét voila.
toegevoegd de auteur NGLN, de bron
Ja, u heeft een eigenschappeneditor nodig. Soms is het vinden van de juiste term de sleutel tot het antwoord.
toegevoegd de auteur TLama, de bron
@NGLN, mijn gedachte was dat de collectie-editor een soort vastgoededitor is. Jerry, alleen om dat duidelijk te maken, dus je wilt de collectie-editor , die op de screenshot komt uit de kolom editor van VirtualStringTree. (+1 voor compensatie)
toegevoegd de auteur TLama, de bron
Niemand verwacht hier dat je alles weet (of wat dan ook, deze site is heel geschikt voor beginners). Wat we echter wel verwachten, is dat je het probleem dat je probeert op te lossen daadwerkelijk kunt beschrijven en genoeg informatie kunt verstrekken zodat mensen je kunnen helpen. :)
toegevoegd de auteur Ken White, de bron
Kijk naar mijn antwoord. Ik heb de bron van de tweede eenheid bewerkt om een ​​tijdelijke verzameling te maken om de ingebouwde CollectionEditor te gebruiken om de items te bewerken. Het is nog steeds niet ideaal omdat je elke eigenschap van TChildComponent aan het TChildComponentCollectionItem moet publiceren.
toegevoegd de auteur Stefan Glienke, de bron

3 antwoord

This Thread helped me creating something as we discussed yesterday. I took the package posted there and modified it a bit. Here is the source:

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.

Het allerbelangrijkste is de RegisterNoIcon die voorkomt dat de component op het formulier wordt getoond wanneer u het maakt. De overschreven methoden in TChildComponent zorgen ervoor dat ze worden genest binnen de TParentComponent.

Bewerken: ik heb een tijdelijke verzameling toegevoegd om de items in de ingebouwde TCollectionEditor te bewerken in plaats van een eigen te moeten schrijven. Het enige nadeel is dat het TChildComponentCollectionItem elke eigenschap die door TChildComponent is gepubliceerd moet publiceren om ze in de OI te kunnen bewerken.

8
toegevoegd
HET IS MOOI!!! Ik ben je allebei te veel verschuldigd.
toegevoegd de auteur Jerry Dodge, de bron
Nieuwe vraag met betrekking tot dit antwoord: stackoverflow.com/questions/8772969/…
toegevoegd de auteur Jerry Dodge, de bron
@Jerry Voeg DesignIDE toe aan het verplichte gedeelte van het bronbestand van uw pakket. Zie Wat is er ooit met Proxies.pas gebeurd? .
toegevoegd de auteur NGLN, de bron
+1 Heel leuk. Ik vermoedde dat het nodig was om Designer.CreateComponent te gebruiken om het element aan het bronbestand toe te voegen, maar Designer.Modified ziet er netjes uit. Goede vondst!
toegevoegd de auteur NGLN, de bron
Heel erg bedankt voor het plaatsen van dit! Ik ben al dagen op zoek naar hoe ik dit precies kan doen :)
toegevoegd de auteur LaKraven, de bron
Ik heb deze oplossing bewerkt om een ​​probleem te verhelpen waarbij het verwijderen van een instantie TParentComponent de instanties TChildComponent op het formulier laat staan.
toegevoegd de auteur LaKraven, de bron

Gebruik de routine 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;

Ik begrijp nu dat dit subonderdeel deel zou uitmaken van een verzamelitem. In dat geval: geen verschil, gebruik deze methode.

2
toegevoegd
Dat zou een TPersistent zijn, wat ook niet is wat ik wil. Als ik slechts 1 exemplaar van dit subonderdeel wilde, dan ja. Maar ik heb een van deze nodig achter elk verzamelitem (of in het algemeen moet ik een lijst met deze exemplaren beheren).
toegevoegd de auteur Jerry Dodge, de bron
Ik zie eigenlijk niet hoe ik de eigenschappen van dit subonderdeel zichtbaar kan maken in Object Inspector, want wat echt is geselecteerd om te laten zien in Object Inspector, is het item in de collectie-editor (TCollectionItem)
toegevoegd de auteur Jerry Dodge, de bron
Dat zou kunnen werken, op een veel grotere schaal natuurlijk. Het voorbeeld gebruikt een enkele subcomponent zonder een verzameling, maar zou waarschijnlijk werken met een onderdeel van een TCollectionItem. Maar zal dit eigenlijk een nieuw component creëren, opgeslagen in de klasse van het formulier?
toegevoegd de auteur Jerry Dodge, de bron
Ja. Een citaat uit mijn link: Tenzij een dergelijke component SetSubComponent met IsSubComponent op True aanroept, worden de gepubliceerde eigenschappen niet in het formulierbestand opgeslagen.
toegevoegd de auteur NGLN, de bron
toegevoegd de auteur NGLN, de bron
Natuurlijk is TColumnTitle van het type TPersistent, maar ik zei: "zal eruit zien als" en betekent ook "zal zich gedragen als". Net zoals er meerdere titels zijn, elk voor elke kolom in een TDBGrid, kunt u meerdere subcomponenten hebben, elk in elk afzonderlijk verzamelitem.
toegevoegd de auteur NGLN, de bron
Wat is het probleem? Deze subcomponent van een verzamelitem zou er exact hetzelfde uitzien als de eigenschap Title van een TColumn in een TDBGrid.Columns. Klik gewoon op [+] in het objectinspector om de subcomponent uit te vouwen met al zijn eigenschappen.
toegevoegd de auteur NGLN, de bron

Implementeer TCollectionItem.GetDisplayName om de verzamelingitems een naam te geven.

En wat betreft de verzameling: wanneer dit een gepubliceerde eigenschap is, wordt de verzameling automatisch de naam van de eigenschap genoemd.

Zorg ervoor dat u GetOwner implementeert wanneer u eigenschappen van TPersistent maakt.

1
toegevoegd
Ik kan het erven van wat dan ook, zolang het maar visueel is, en het kan een naam hebben in de klasse van het formulier - ik neem aan dat componenten het meest geschikt zijn, maar corrigeer me als ik het mis heb.
toegevoegd de auteur Jerry Dodge, de bron
Nee, ik bedoel niet 'Display Name'. Ik ben me daar meer dan bewust van. Ik moet een andere onzichtbare component maken om elk item, een benoemde component, te vertegenwoordigen.
toegevoegd de auteur Jerry Dodge, de bron
Wat is de reden dat het moet erven van TComponent?
toegevoegd de auteur Stefan Glienke, de bron