Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi Komponente mit TStrings Property erstellen (https://www.delphipraxis.net/76758-komponente-mit-tstrings-property-erstellen.html)

GuenterS 9. Sep 2006 08:59


Komponente mit TStrings Property erstellen
 
Hallo,

wie der Titel schon aussagt, habe ich ein kleines Problem beim Erstellen einer Komponente, welche ein TStrings Objekt als Property im OI anbieten soll.

Ich habe mich beim Erstellen, an diverse Tutorials gehalten (zumindest versucht) aber jedesmal, wenn ich die Komponente dann auf ein Formular ziehe, der Property SQL (das ist die TStrings Property) einen String zuweise und das ganze dann noch ein zweites mal versuche, bekomme ich Zugriffsverletzungen, so, dass nur noch das Beenden von Delphi hilft.

Ich verwende Delphi 7 Professional.

Delphi-Quellcode:
unit GSQuery;

interface

uses
  SysUtils, Classes;

type
  TGSQuery = class(TComponent)
  private
    FSQL: TStringList;
    procedure setSQL(const Value: TStringList);
    { Private-Deklarationen }
  protected
    { Protected-Deklarationen }
  public
    { Public-Deklarationen }
    constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;
  published
    { Published-Deklarationen }
    property SQL: TStringList read FSQL write setSQL;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('GS', [TGSQuery]);
end;

{ TGSQuery }

constructor TGSQuery.Create(aOwner: TComponent);
begin
  inherited;
  FSQL := TStringList.Create;
end;

destructor TGSQuery.Destroy;
begin
  FSQL.Free;
  inherited;
end;

procedure TGSQuery.setSQL(const Value: TStringList);
begin
  FSQL := Value;
end;

end.
Was mache ich da nur falsch?

Hawkeye219 9. Sep 2006 09:35

Re: Komponente mit TStrings Property erstellen
 
Hallo Günter,

der Fehler steckt in der Setter-Methode. Durch die direkte Zuweisung überschreibst du die im Constructor initialisierte Referenz auf das TStringList-Objekt mit einem neuen Zeiger, was zunächst nur zu einem Speicherleck führt. Gleichzeitig übernimmt aber nun das Query-Objekt die Verantwortung für die übergebene Liste, die du vermutlich nach der Zuweisung zur SQL-Eigenschaft freigibst. Damit ist dann der Zeiger innerhalb des Query-Objekts ungültig!

Besser ist es, im Setter den Inhalt der Liste zu kopieren:

Delphi-Quellcode:
FSQL.Assign(Value);
Noch ein Vorschlag: ändere den Datentyp von FSQL ab in TStrings. Dadurch kannst du später beliebige TStrings-Nachfolger (z.B. TMemo.Lines) zuweisen. Im Constructor von TGSQuery muß natürlich weiterhin ein TStringList-Objekt instanziiert werden, da TStrings abstrakte Methoden enthält.

Gruß Hawkeye

GuenterS 9. Sep 2006 09:54

Re: Komponente mit TStrings Property erstellen
 
Danke :)

Wenn ich Assign verwende klappt es, ein Query Objekt kommt aber nicht wirklich vor, das ist wirklich der komplette Quelltext der Komponente ... die Query kommt erst später dazu :)


Das mit TStrings habe ich aber nicht hinbekommen, da meckert der Compiler immer über inkompatible Typen.

Delphi-Quellcode:
unit GSQuery;

interface

uses
  SysUtils, Classes;

type
  TGSQuery = class(TComponent)
  private
    FSQL: TStringList;
    procedure setSQL(const Value: TStrings);
    { Private-Deklarationen }
  protected
    { Protected-Deklarationen }
  public
    { Public-Deklarationen }
    constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;
  published
    { Published-Deklarationen }
    property SQL: TStrings read FSQL write setSQL;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('GS', [TGSQuery]);
end;

{ TGSQuery }

constructor TGSQuery.Create(aOwner: TComponent);
begin
  inherited;
  FSQL := TStringList.Create;
end;

destructor TGSQuery.Destroy;
begin
  FSQL.Free;
  inherited;
end;

procedure TGSQuery.setSQL(const Value: TStrings);
begin
  FSQL.Assign(Value);
end;

end.

Hawkeye219 9. Sep 2006 10:04

Re: Komponente mit TStrings Property erstellen
 
Du mußt die Deklaration im private-Abschnitt ebenfalls ändern:

Delphi-Quellcode:
private
  FSQL: TStrings;
Falls du innerhalb der Komponente einmal auf die TStringList-Eigenschaften zugreifen mußt, benötigst du einen TypeCast:

Delphi-Quellcode:
TStringList(FSQL).Sorted := True;
Bei der SQL-Eigenschaft sehe ich diese Notwendigkeit aber nicht.

PS: mit "Query-Objekt" meinte ich eine Instanz deiner TGSQuery-Klasse.

Gruß Hawkeye

GuenterS 9. Sep 2006 10:23

Re: Komponente mit TStrings Property erstellen
 
Dankeschön, jetzt funktioniert das mal soweit wie ich es wollte.

Poolspieler 18. Mär 2013 20:29

AW: Komponente mit TStrings Property erstellen
 
Hallo zusammen,
das Thema ist zwar schon älter - ich habe aber bei gleichem Source-Code folgendes Problem:

Im OI kann ich schön Strings in die Stringliste eintragen (mit dem automatisch öffnenden String-Listen-Editor).
Soweit so gut.

Wenn ich das Programm aber starte, dann sind die eingetragenen Werte nicht mehr vorhanden --> gibt es da einen Trick?
Wo sollen die auch her kommen?:shock:

Code:
constructor TGSQuery.Create(aOwner: TComponent);
begin
   inherited;
   FSQL := TStringList.Create;
end;
Im Konstruktor wird FSQL erzeugt --> ist also LEER.:?
WIE komme ich nun an die zur Designzeit eingetragenen Werte?:cyclops:

Wäre schön, wenn jemand eine Antwort für mich hätte.

Viele Grüße,

Poolspieler

Poolspieler 18. Mär 2013 21:15

AW: Komponente mit TStrings Property erstellen
 
Hallo,
ich bin ein bisschen weiter:

In FSQL sind die Werte schon vorhanden. Allerdings erst nach einem noch unbekannten Zeitpunkt.

Problem ist, dass ich versuche, diese Werte im CONSTRUCTOR einer Combobox zuzuweisen.
Und zum Zeitpunkt des Konstruktors ist FSQL tatsächlich noch LEER!

Code:
constructor TGSQuery.Create(aOwner: TComponent);
 begin
    inherited;
    FSQL := TStringList.Create;
 end;
combobox.Items.Assign(FSQL);
Frage:
WANN werden die Werte nach FSQL geladen?
Ich habe schon das Ereignis AfterConstruction probiert --> da ist FSQL noch LEER!
Mit dem Debugger konnte ich den Zeitpunkt auch noch nicht finden. :(

Hat jemand eine Idee?

Viele Grüße,

Poolspieler

Volker Z. 18. Mär 2013 21:33

AW: Komponente mit TStrings Property erstellen
 
Hallo,

hast Du denn einen Setter für FSQL in Deiner Klasse TGSQuery? Schau Dir mal den Quellcode in Post #3 an; da sollte die Lösung stehen.

Gruß

Poolspieler 18. Mär 2013 21:49

AW: Komponente mit TStrings Property erstellen
 
Hallo Volker,
danke für Deine Antwort!

Ja, habe ich schon.

Allerdings stand ich auch gerade ganz schön auf der Leitung:

Im Konstruktor muss es natürlich heißen:
Code:
constructor TGSQuery.Create(aOwner: TComponent);
  begin
     inherited;
     ...
     combobox := TComboBox.Create(Self);
     ...
     FSQL := combobox.Items;
     // und NICHT:
     //FSQL := TStringList.Create;
  end;
Was will ich denn mit ZWEI Listen???????? :stupid:
Es ist halt doch schon spät...

Viele Grüße,

Poolspieler

Gutelo 19. Jan 2014 07:37

AW: Komponente mit TStrings Property erstellen
 
Noch eine weiterfuehrende Frage,

ich habe auch eine TStrings Liste in den Properties einer eigenen Komponente. Zur Designzeit werden die eingetragenen Werte in der Liste durch eine Setter procedure uebernommen. Nun werden die eigentragenen Werte in der Stringliste dazu verwendet ein Array zu fuellen (wird ebenfalls in der Setter procedure erledigt). Das funktioniert waehrend des Designs prima, allerdings ist das Array zur Laufzeit wieder leer.

Die Komponente ist von TPaintBox abegeleitet. Da das Array vom Typ record ist und einige werte zur Laufzeit geaendert werden kann ich das Erstellen des Arrays nicht einfach in die paint procedure packen, da sonst jedesmal standardwerte im Array stehen.

Gibt es eine procedure die nur einmal nach dem constructor ausgefuehrt wird?

Gutelo


Edit: Habs jetzt so geloest in der paint procedure auch wenns nich ganz optimal ist:

if Length(Arr) = 0 then FillArr(SList, Arr);

Arr ist das Array, SList ist TStrings mit Werten und FillArr eine Prozedur die die Werte aus SList in Arr eintraegt.

Sir Rufo 19. Jan 2014 08:27

AW: Komponente mit TStrings Property erstellen
 
Delphi-Referenz durchsuchenTObject.AfterConstruction ;)

Gutelo 19. Jan 2014 09:05

AW: Komponente mit TStrings Property erstellen
 
hmm, wenn ich ein override bei AfterConstruction durchfuehre und meinen Code eintrage dann passiert leider garnichts :(

Edit: hmm, ein showmessage zeigt mir dass er AfterConstruction abarbeitet, aber das Array wird nicht gefuellt

Sir Rufo 19. Jan 2014 11:11

AW: Komponente mit TStrings Property erstellen
 
Zitat:

Zitat von Gutelo (Beitrag 1244300)
hmm, wenn ich ein override bei AfterConstruction durchfuehre und meinen Code eintrage dann passiert leider garnichts :(

Edit: hmm, ein showmessage zeigt mir dass er AfterConstruction abarbeitet, aber das Array wird nicht gefuellt

Delphi-Quellcode:
AfterConstruction
kann aus nicht funktionierendem Code auch keinen funktionierenden zaubern ;)

Zeig doch mal den Code und mindestens den interface Teil deiner Komponente

Gutelo 19. Jan 2014 16:17

AW: Komponente mit TStrings Property erstellen
 
Hallo Rufo,

ich stell das Problem mal kurz dar mit Auszug aus meiner Komponente

Code:

unit MyComponent;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Dialogs, ExtCtrls, Math, StrUtils;

type TPanelButton = record
    Left : integer;
    Top : integer;
    Width : integer;
    Height : integer;
    Caption : String;
    BorderColor : TColor;
    FillColor : TColor;
    FillCheckColor : TColor;
    UnCheckedFont : TFont;
    CheckedFont : TFont;
    Checked : boolean;
  end;

  Type TPanelButtons = Array of TPanelButton;
 
  ...

  MyComponent = class(TPaintBox)
  private
    VButtonList : TStringList;
    ...
    procedure SetButtonList(Value: TStringList);
    AllButtons : TPanelButtons;

  published
    ...
    property ButtonList : TStringList read VButtonList write SetButtonList;
  public
    procedure ReadButtons(List : TStrings; var BTNArr : TPanelButtons);
  ...
  implementation
  ...

  constructor MyComponent.Create(AOwner: TComponent);
  begin
    inherited Create(AOwner);
    VButtonList := TStringList.create;
    ...
  end;

  procedure MyComponent.SetButtonList(Value: TStringList);
  begin
     VButtonList.Assign(Value);
     ReadButtons(VButtonList, AllButtons);
     refresh;
  end;

  procedure MyComponent.ReadButtons(List : TStrings; var BTNArr : TPanelButtons);
  var
    i: Integer;
    PosKomma : integer;
    PosStart : integer;
  begin
    // Format of Button Strings: Caption,Left,Width,Top,Height
    SetLength(BTNArr, List.count);
    for i := 0 to List.Count - 1 do
    begin
      // Caption
      PosStart := 1;
      PosKomma := Pos(',', List[i]);
      BTNArr[i].Caption := Copy(List[i],PosStart,PosKomma-PosStart);
      // Left
      PosStart := PosKomma+1;
      PosKomma := PosEx(',', List[i], PosStart);
      BTNArr[i].Left := StrToInt(Copy(List[i],PosStart,PosKomma-PosStart));
      // Top
      PosStart := PosKomma+1;
      PosKomma := PosEx(',', List[i], PosStart);
      BTNArr[i].Width := StrToInt(Copy(List[i],PosStart,PosKomma-PosStart));
      // Width
      PosStart := PosKomma+1;
      PosKomma := PosEx(',', List[i], PosStart);
      BTNArr[i].Top := StrToInt(Copy(List[i],PosStart,PosKomma-PosStart));
      // Height
      PosStart := PosKomma+1;
      PosKomma := Length(List[i])+1;
      BTNArr[i].Height := StrToInt(Copy(List[i],PosStart,PosKomma-PosStart));
      // Other Button Options
      BTNArr[i].UnCheckedFont := TFont.Create;
      BTNArr[i].UnCheckedFont.Color := clBlue;
      BTNArr[i].CheckedFont := TFont.Create;
      BTNArr[i].CheckedFont.Color := clWhite;
      BTNArr[i].BorderColor := clBlue;
      BTNArr[i].FillColor := clWhite;
      BTNArr[i].FillCheckColor := clBlue;
      BTNArr[i].Checked := false;
    end;
  end;

  ...
  procedure MyComponent.paint();
  begin
     ... hier werden die buttons aus dem Array gezeichnet
  end;
Wie vorher schon gesagt reicht das "ReadButtons(VButtonList, AllButtons);" in SetButtonList nicht aus. Das AllButtons Array, welches zur Designzeit gefuellt wird, ist zur Laufzeit wieder leer. Auch wenn ich "ReadButtons(VButtonList, AllButtons);" in AfterConstruction packe bleibt das Array leer.

Ich vermute, dass es am "Array of record" liegt. Bei anderen Strukturen bleiben die Werte zwischen Designzeit und Laufzeit erhalten.

Sir Rufo 19. Jan 2014 16:38

AW: Komponente mit TStrings Property erstellen
 
Aha, zur DesignTime und zur RunTime.

Wo sagst du denn, dass das ButtonArray auch gespeichert werden soll?
Oder wie denkst du, dass zur RunTime noch was aus der DesignTime bekannt ist?
Nachschauen, ja aber wo denn?
Diese Informationen müssen in der .dfm stehen

Und idR werden solche Unterkomponenten in variabler Anzahl als Collection gespeichert (die werden dann auch automatisch in der .dfm abgelegt und wieder ausgelesen, wenn published)
Schau dir dazu mal ein paar Komponenten an, die solche Unterkomponenten haben (z.B. Delphi-Referenz durchsuchenTListView.Columns)

Gutelo 19. Jan 2014 17:03

AW: Komponente mit TStrings Property erstellen
 
Naja, die Eintraege der TStringList bleiben ja auch zur Runtime erhalten. Und ich habe eine weitere Stringlist die nicht in einem Array gespeichert wird sondern nur in einem Record:

type TPanelGraph = record
Left : integer;
Top : integer;
Width : integer;
Height : integer;
Gap : integer;
BorderColor : TColor;
FillColor : TColor;
FillCheckColor : TColor;
Values : Array of integer;
end;

Und in diesem Fall sind die Designzeit Eintraege ebenfalls noch zur Runtime vorhanden.

Gutelo 19. Jan 2014 17:09

AW: Komponente mit TStrings Property erstellen
 
Aaargs, nun reicht es doch die Werte mit der Setter prozedur zur Designzeit zu setzen und die Array werte sind zur Laufzeit noch da. Kann leider nicht sagen warum es jetzt geht.


Alle Zeitangaben in WEZ +1. Es ist jetzt 07:51 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz