Dynamisches Array mit PropertyEditor in eigener Komponente

Rund um die LCL und andere Komponenten
Antworten
wennerer
Beiträge: 390
Registriert: Di 19. Mai 2015, 20:05
OS, Lazarus, FPC: Linux Mint 20 Cinnamon, Lazarus Stable 2.2.0 (rev lazarus_2_2_0) FPC 3.2.2 x86_
CPU-Target: x86_64-linux-gtk2

Dynamisches Array mit PropertyEditor in eigener Komponente

Beitrag von wennerer »

Hallo zusammen,
ich brauche mal wieder Unterstützung. Ich habe eine kleine Komponente mit einem Propertyeditor erzeugt. Im Propertyeditor kann ich ein Polygon zeichnen und dieses wird in die Komponente übernommen. Lieder schaffe ich es nicht das Array in die lfm zu streamen. Ich weiß auch nicht ob das überhaupt möglich ist. Momentan streame ich das Array in eine Datei und lade die dann wieder. Bei der Variante habe ich aber das Problem das ich zur Designzeit noch nicht weiß welchen Pfad ich verwenden kann. Um mir zu behelfen speichere ich momentan in das Homeverzeichnis. Das kann aber nicht der richtige Weg sein.
Falls jemand weiß wie man das macht würde ich mich über eine Antwort sehr freuen. Ich hänge mal meine kleine Komponente an. Sie wird unter misc angezeigt.

Viele Grüße
Bernd
CustomShape.png
CustomShape.png (121.06 KiB) 1192 mal betrachtet
Dateianhänge
TCustomShape.zip
(113.39 KiB) 42-mal heruntergeladen

Benutzeravatar
theo
Beiträge: 9580
Registriert: Mo 11. Sep 2006, 19:01

Re: Dynamisches Array mit PropertyEditor in eigener Komponente

Beitrag von theo »

Ohne mich tiefer damit zu befassen oder den Sinn deines Vorhabens verstehen zu wollen:
Hast du das gelesen?
https://wiki.lazarus.freepascal.org/Str ... ponents/de

Zum "üblichen" Speicherort für die Konfiguration:
https://lazarus-ccr.sourceforge.io/docs ... igdir.html

Benutzeravatar
Winni
Beiträge: 1423
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: Dynamisches Array mit PropertyEditor in eigener Komponente

Beitrag von Winni »

Hi!

Zu dem Speichern eines Arrays in der lfm Datei:

Ich habe noch nie ein Array in einer lfm gesehen.
Aber die Lösung ist einfach:
Es gibt diverse Komponenten, dessen Werte in einer Stringlist gespeichert werden.

Code: Alles auswählen

Items.Strings = (
      'Emboss'
      'Cascade'
      'Sandwich'      
      ...
      )


Also Deine Koordinaten als string in X,Y in einer Stringlist speichern

Winni

wp_xyz
Beiträge: 4427
Registriert: Fr 8. Apr 2011, 09:01

Re: Dynamisches Array mit PropertyEditor in eigener Komponente

Beitrag von wp_xyz »

Das sieht mir etwas verunglückt aus. Denn dein Property-Editor ist für die Ausgabe und die Maus-Interaktion zuständig. Das ist schlecht, denn was machst deine Shape-Komponente zur normalen Laufzeit, wenn der Property-Editor gar nicht zur Verfügung steht?

Als erstes: warum ist TMyShape ein TObject und kein TGraphicControl oder TWinControl? Diese können sich selbst zeichnen und kennen die Maus, so dass du diesen Code in die Komponente verlagern kannst. Als Konsequenz kann sich dann TMyShape selbst um die Ausgabe auf seinem/ihrem eigenen Canvas und das Hinzufügen von Punkten kümmern. TMyShape hat wahrscheinlich eine Array-ähnliche Eigenschaft "Points" (oder so ähnlich) für die Polygon-Punkte. Zuständig für den Streaming-Mechanismus neuer Property-Typen ist die Methode DefineProperties, die die Schreib- und Lese-Methoden definiert, wie die Property-Daten in der lfm-Datei geschrieben/gelesen werden. Das Verfahren wird z.B. in dem Example-Projekt "widestringstreaming" durchgespielt, sowie in diversen LCL-Komponenten (TGridColumnTitle in grids). Auch TStrings hat ein DefineProperties. Da es wenig Sinn macht, die Punkt-Koordinaten binär in die lfm-Datei zuschreiben, müssen die von DefineProperties festgelegten Schreib-/Lesemethoden die Koordinaten in Strings umwandeln.

Ohne jetzt viel ausprobiert zu haben, hier die grobe Struktur:

Code: Alles auswählen

type
  TMyshapePoints = array of TPoint;
  TMyShape = class(TGraphicControl)
  private
    FPoints: TMyShapePoints;
    procedure ReadPoints(Filer: TFiler); 
    procedure Writepoints(Filer: TFiler);
    function GetPoint(AIndex: Integer): TPoint;
    procedure Setpoint(AIndex: Integer; AValue: TPoint);
  protected
    procedure DefineProperties(Filer: TFiler); override;
    procedure Paint; override;
    procedure MouseDown(...); override;
    procedure MouseMove(...); override;
    procedure MouseUp(...); override;
    ...
  published
    property Points[Index: Integer]: TPoint read GetPoint write Setpoint;
  end;
  
procedure TMyShape.DefineProperties(Filer: TFiler);
begin
  inherited DefineProperties(Filer);
  Filer.DefineProperty('Points',  @ReadPoints,  @WritePoints, true);
end;

procedure TMyShape.WritePoints(Writer: TWriter);
var
  L: TStrings;
begin
  L := TStringList.Create;
  try
    for i := 0 to High(FPoints) do
      L.Add('%d,%d', [FPoints[i].X, FPoints[i].Y]);
    L.WriteData(Writer);
  finally
    L.Free;
  end;
end;

procedure TMyshape.ReadPoints(Reader: TReader);
var
  L: TStrings;
  sa: TStringArray;
begin
  L := TStringList.Create;
  try
    L.ReadData(Reader);
    Setlength(FPoints, L.Count);
    for i := 0 to L.Count-1 do
    begin
      sa := L[i].Split(',');
      FPoints[i].X := StrToInt(sa[0]);
      FPoints[i].Y := StrToInt(sa[1]);
    end;
  finally
    L.Free;
  end;
end;

function TMyShape.GetPoint(AIndex: Integer): TPoint;
begin
  Result := FPoints[AIndex];
end;

procedure TMyShape.Setpoint(AIndex: Integer; AValue: TPoint);
begin
  FPoints[AIndex] := AValue;
end;
Wenn du dann später die Punkte zur Designzeit eingeben willst, brauchst du einen PropertyEditor für TMyshapePoints (ich sehe gerade: möglicherweise muss die Eigenschaft Points als TMyShapePoints deklariert sein...). Ähnlich wie du es schon gemacht hast, erzeugt der ein eigenes Formular, in dem eine Instanz von TMyShape kreiert wird. Mit den Methoden von TMyShape wird diese Instanz gezeichnet, und mit den Mausmethoden werden neue Punkte hinzugefügt usw. Das ganze ist relativ kompliziert und wenig dokumentiert... Am besten du siehst dir die bereits bestehenden PropertyEditoren in der Unit PropEdits an

[EDIT]
Eine andere Variante fällt mir noch ein, nämlich die Punkte nicht in einem dynamischen Array sondern in einer Collection zu speichern. TCollection und Nachfahren sind sowas wie TList oder meinetwegen auch ein Array, aber halt integriert in die IDE. Die einzige Grundbedingung ist, dass die Collection-Items Nachfahren von TCollectionItem sein müssen, das heißt du musst eine Wrapper-Klasse für TPoint schreiben. Aber ansonsten ist alles fertig, auch die Integration in den Objekt-Inspektor. DefineProperties ist nicht mehr nötig.

Code: Alles auswählen

type
  TPointCollectionItem = class(TCollectionItem)
  private
    FPoint: TPoint;
    function GetX: Integer;
    function GetY: Integer;
    procedure SetX(Value: Integer);
    procedure SetY(Value: Integer);
  public
    procedure Assign(ASource: TPersistent); override;
  published
    property Point: TPoint read FPoint write FPoint;
    property X: Integer read GetX write SetX;
    property Y: Integer read GetY write SetY;
  end;
    
  TPointCollection = class(TCollection)
  private
    function GetItem(AIndex: Integer): TPointCollectionItem;
    procedure SetItem(AIndex: Integer; AValue: TPointCollectionItem);
  public
    constructor Create;
    property Items[AIndex: Integer]: TPointCollectionItem read Getitem write SetItem;
  end;
  
procedure TPointCollectionItem.Assign(ASource: TPersistent);
begin
  if ASource is TPointCollectionItem then
  begin
    FPoint := ASource.Point;
    inherited Assign(ASource);
  end else
    raise Exception.Create('Assign funktioniert nur für TPointCollectionItem');
end;

function TPointCollectionItem.GetX: Integer;
begin
   Result := FPoint.X;
end;

procedure TPointCollectionItem.SetX(Value: Integer);
begin
  FPoint.X := Value;
end;

constructor TPointCollection.Create;
begin
  inherited Create(TPointCollectionItem);
end;

function TPointCollection.GetItem(AIndex: Integer): TPointCollectionItem;
begin
  Result := TPointCollectionItem(inherited GetItem(AIndex));
end;

procedure TPointCollection.SetItem(AIndex: Integer; AValue: TPointCollectionItem);
begin
  inherited SetItem(AIndex, AValue);
end;
Zuletzt geändert von wp_xyz am Sa 11. Jun 2022, 13:17, insgesamt 1-mal geändert.

wennerer
Beiträge: 390
Registriert: Di 19. Mai 2015, 20:05
OS, Lazarus, FPC: Linux Mint 20 Cinnamon, Lazarus Stable 2.2.0 (rev lazarus_2_2_0) FPC 3.2.2 x86_
CPU-Target: x86_64-linux-gtk2

Re: Dynamisches Array mit PropertyEditor in eigener Komponente

Beitrag von wennerer »

Vielen Dank für eure Antworten!
@ Theo: Die Links sind sehr interessant für mich. Habe ich leider selber nicht gefunden (war wohl mal wieder zu blöd zum Googeln :D ).
@ Winni: Die Werte als Strings zu speichern ist auf jeden Fall eine Option. Daran hab ich noch gar nicht gedacht.
@ wp_xyz: Das ist beeindruckend wie du so eine Antwort aus dem Ärmel schüttelst. Ich werde deine Vorschläge bestimmmt alle der Reihe nach probieren. Mal schauen wie weit ich komme.

Nur noch zum besseren Verständniss. Eigentlich werkel ich an einem Panel herum. Geht auch schon einiges wie ich will. Jetzt kam mir der Gedanke das ich dem Panel zur Designtime eine eigene Form geben kann. Darüber ob ich die dann zur Laufzeit nochmal ändern möchte habe ich ehrlich gesagt noch nicht nach gedacht. Über den Sinn kann man da natürlich streiten, aber ich mach es ja zum Spaß und es hat mich halt gereizt. Die hier gepostete kleine Komponente nutze ich eigentlich nur um Verschiedenes zu testen. Aber eventuell wird ja noch mehr daraus. Jedenfalls hab ich nun reichlich zum Nachdenken!

Viele Grüße
Bernd

wennerer
Beiträge: 390
Registriert: Di 19. Mai 2015, 20:05
OS, Lazarus, FPC: Linux Mint 20 Cinnamon, Lazarus Stable 2.2.0 (rev lazarus_2_2_0) FPC 3.2.2 x86_
CPU-Target: x86_64-linux-gtk2

Re: Dynamisches Array mit PropertyEditor in eigener Komponente

Beitrag von wennerer »

Hallo zusammen,
ich denke ich habe nun (nach etlichen Versuchen) eine Variante gefunden die das macht was ich gerne möchte. Jedenfalls wird das Array (als Strings) problemlos in die lfm geschrieben und gelesen. Ist zwar sicherlich etwas speziell, aber vielleicht interessiert es ja doch irgend jemanden. Der angehängte Test wird unter Misc angezeigt.

Ein weiteres Problemchen konnte ich leider noch nicht lösen. Ich möchte das durch doppelklick auf die Komponente der Editor aufgeht. Ich dachte eigentlich das normalerweise automatisch ein Doppelklick in der Edit Methode des PropertyEditors landet. Ist wohl leider nicht so. Ich habe dann auch probiert, durch überschreiben von GetVerbCount, GetVerb und ExecuteVerb im Kontextmenü einen Menüeintrag hinzuzufügen. Das funktioniert leider auch nicht. Bei einem KomponentenEditor hab ich es schon problemlos hinbekommen. Weiß vielleicht jemand wo da der Unterschied liegt bzw. was ich machen muss?

Viele Grüße
Bernd
Dateianhänge
TCustomShape2.zip
(63.04 KiB) 30-mal heruntergeladen
Zuletzt geändert von wennerer am Fr 26. Aug 2022, 15:02, insgesamt 1-mal geändert.

wp_xyz
Beiträge: 4427
Registriert: Fr 8. Apr 2011, 09:01

Re: Dynamisches Array mit PropertyEditor in eigener Komponente

Beitrag von wp_xyz »

wennerer hat geschrieben:
So 21. Aug 2022, 20:43
Ich möchte das durch doppelklick auf die Komponente der Editor aufgeht.
Für das Verhalten bei einem Doppelklick auf der Komponente ist nicht der PropertyEditor, sondern der Komponenten-Editor zuständig: https://wiki.freepascal.org/How_To_Writ ... nt_editors. Das Grundgerüst dafür (mit integrierter Kurz-Doku) und all die Beispiele aus der LCL findest du in der Datei componenteditors.pas im Verzeichnis components/ideintf der Lazarus-Installation.
Zuletzt geändert von wp_xyz am Mo 22. Aug 2022, 17:08, insgesamt 1-mal geändert.

wennerer
Beiträge: 390
Registriert: Di 19. Mai 2015, 20:05
OS, Lazarus, FPC: Linux Mint 20 Cinnamon, Lazarus Stable 2.2.0 (rev lazarus_2_2_0) FPC 3.2.2 x86_
CPU-Target: x86_64-linux-gtk2

Re: Dynamisches Array mit PropertyEditor in eigener Komponente

Beitrag von wennerer »

Hallo,
wp_xyz schrieb:
Für das Verhalten bei einem Doppelklick auf der Komponente ist nicht der PropertyEditor, sondern der Komponenten-Editor zuständig
Okay, wenn man drüber nachdenkt macht das ja auch Sinn. Ein PropertyEditor ist ja nur für eine Eigenschaft zuständig. Ich hab mich da wahrscheinlich blenden lassen weil es im PropertyEditor auch die Methoden GetVerbCount, GetVerb und ExecuteVerb gibt die beim KomponentenEditor für das Kontextmenü überschrieben werden.
Jedenfalls mal wieder vielen Dank für deine Hilfe!

Viele Grüße
Bernd

Antworten