Fragen zu TVirtualStringTree

Für Fragen von Einsteigern und Programmieranfängern...
Benutzeravatar
Jim Knopf
Beiträge: 98
Registriert: So 18. Mai 2014, 15:16
OS, Lazarus, FPC: Win10
CPU-Target: 64Bit
Wohnort: Klagenfurt
Kontaktdaten:

Fragen zu TVirtualStringTree

Beitrag von Jim Knopf »

Hallo zusammen,

nach langen Vergleichen diverser Grids, Dokulesen und schlaflosen Nächten glaube ich, dass TVirtualStringTree doch die sinnvollste Variante ist, um mein Projekt Patchwork (https://autorenprogramm.com/) auf Lazarus zu bekommen. Zur Erinnerung an den Sommer von 2021: Das ist das Programm für Schriftsteller. Damals hat wp_xyz ein wunderbares Beispiel gemacht, anhand dessen ich sehen konnte, wie VST grundlegend funktioniert. Das war sehr hilfreich und ich möchte mich noch einmal herzlich dafür bedanken!

Übrigens seinerzeit war mein Gedanke nie, das DevEx-Grid einfach zu konvertieren, was, glaube ich, wegen der vielen direkten WinAPI-Tricksereien gar nicht geht (also für mich schon, aber nicht zur Weitergabe, wäre es möglich gewesen), sondern nur, um die Logik nachzuvollziehen. Denn die, also dass es Spalten unterschiedlichen Typs gibt (String, Image, Checkbox ...), Nodes mit beliebiger Tiefe von Childnodes, die die Daten per Index der Spalten vorhalten und dann das Ganze einfach per SaveToFile und LoadFromFile gespeichert/geladen werden kann, ist m.E. genial einfach, samt dem simplen Zugriff auf die Knoten nach unterschiedlichen Kriterien. Dazu noch ein paar Events wie OnCustomDrawCell, Maus- und Tastaturevents, OnChangeNode und man kann unglaublich viel damit machen.

Es würde mir sehr helfen, für VST bei mir neue neuronale Verknüpfungen aufzubauen, wenn ihr mir vorher bei ein paar Sachen helfen könntet.

1. VST ist ja ein virtuelles Grid, das heißt, es ist dafür gedacht, Daten erst on demand - was bei einer Datenbank sehr sinnvoll ist - zu laden. Ich verwende aber keine Datenbank. Das ist zum Glück in meiner Vergangenheit des Bau-Branchenpakets (KEOPS)zurückgeblieben, das jetzt eine andere Firma weiterführt. Im Beispiel von wp_xyz sieht man, das diese On-demand-Sache aber nicht genutzt werden muss. Aber doch irgendwie ...? Die Methoden SaveToFile und LoadFromFile behandeln anscheinend nur die Knoten und von denen den ersten(?) Text - ist also zur Datenvorhaltung anscheinend nicht brauchbar. Mit SaveToCVSFile sieht es schon besser aus. Aber es gibt kein LoadFromCSVFile. Insofern verständlich, weil es ja nicht einmal Information zur horizontalen Einrückung in der CSV gibt. Oder braucht es eine Kombination von beidem ...? Und wenn ja, wie? Oder muss man sich doch was Eigenes dafür schnitzen?

2. Ich habe irgendwo gelesen, dass jemand nicht mit den Records arbeitet, sondern mit Objekten, was mir viel lieber wäre. Denn ich habe keine Grids, die größer sind als 5000 Knoten. Selbst das würde 5000 Szenen bedeuten, was ein Projekt mit zehn Bänden zu 300 Seiten wäre - Maximum bis dato einer Anwenderin ist die Hälfte.

3. Bezugnehmend auf 2: Was wäre eure Idee für einen komfortablen Überbau zu VST, mit dem man bestehende Knoten speichern und laden kann?

4. Gibt es irgendwo eine handlichere Doku als https://documentation.help/VirtualTreeview/ wo man sich einen Wolf scrollt (und bei der Hälfte 'Not documented' steht) idealerweise einfach Properties, Methoden, Ereignisse? Denn so bin ich ständig am Source-Grundeln, was extrem zeitaufwendig ist.

5. Mit welcher Methode bekomme ich den gerade gewählten Knoten zurück? GetFirstSelected ...?

6. Wie realisiert man das Verschieben von Knoten, also zwischen zwei bestehende bzw. einem bestehenden untergeordnet?

Ich belasse es derweil bei diesen 6 Punkten, später mehr, wenn es euch recht ist. Lieber im selben Thread oder jedesmal einen neuen?

Viele Grüße
Martin

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

Re: Fragen zu TVirtualStringTree

Beitrag von theo »

Nur kurz ein Hinweis: Die Beispiele in "VirtualTreeView/Demos/" hast du dir schon angesehen?
Ich denke von der Demo "datarray" könntet du dir schon viel abgucken.

Benutzeravatar
Jim Knopf
Beiträge: 98
Registriert: So 18. Mai 2014, 15:16
OS, Lazarus, FPC: Win10
CPU-Target: 64Bit
Wohnort: Klagenfurt
Kontaktdaten:

Re: Fragen zu TVirtualStringTree

Beitrag von Jim Knopf »

Demos hatte ich nicht, VST neu heruntergeladen, da war ein Demo-Ordner mit dabei. Allerdings ist dort keine DataArray. Aber vielleicht finde ich ja was.

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1436
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Fragen zu TVirtualStringTree

Beitrag von fliegermichl »

Ich habe mal ein kleines Beispiel mit Klassen gebastelt. Das kann die Daten auch speichern und laden.
Wenn man "Kunde neu anklickt" Sind die Datenfelder mit F2 bearbeitbar.
Dateianhänge
vstsample.zip
Minianwendung mit TVirtualStringTree und Klassen als Daten.
(140.65 KiB) 47-mal heruntergeladen

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

Re: Fragen zu TVirtualStringTree

Beitrag von theo »

Jim Knopf hat geschrieben:
Sa 13. Jan 2024, 14:29
Demos hatte ich nicht, VST neu heruntergeladen, da war ein Demo-Ordner mit dabei. Allerdings ist dort keine DataArray. Aber vielleicht finde ich ja was.
https://github.com/blikblum/VirtualTree ... s-v5/Demos

Benutzeravatar
Jim Knopf
Beiträge: 98
Registriert: So 18. Mai 2014, 15:16
OS, Lazarus, FPC: Win10
CPU-Target: 64Bit
Wohnort: Klagenfurt
Kontaktdaten:

Re: Fragen zu TVirtualStringTree

Beitrag von Jim Knopf »

Danke Fliegermichl für das Beispiel. Dummerweise kommt bei Zeile 96 (CellText := Kunde.GetText(Column);) ein Fehler, sobald ich auf [Kunde neu] klicke. Weil er das Objekt noch nicht hat?

Er kommt zuerst bei vstInitNode durch, meint aber dort offenbar, dass vst.GetNodeData(Node) nicht nil ist. Bei den überwachten Ausdrücken meint er dazu: '<Error: calling functions not allow ...'. Darf man dort generell keine Funktionsergebnisse abrufen?

Beim ersten Kompilieren kommt diese Meldung.

Bild

(Habe Dwarf 2 with sets genommen. Aber das hat damit sicher nichts zu tun)

Benutzeravatar
Jim Knopf
Beiträge: 98
Registriert: So 18. Mai 2014, 15:16
OS, Lazarus, FPC: Win10
CPU-Target: 64Bit
Wohnort: Klagenfurt
Kontaktdaten:

Re: Fragen zu TVirtualStringTree

Beitrag von Jim Knopf »

Danke theo, schau ich mir gleich an.

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1436
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Fragen zu TVirtualStringTree

Beitrag von fliegermichl »

Jim Knopf hat geschrieben:
Sa 13. Jan 2024, 15:22
Danke Fliegermichl für das Beispiel. Dummerweise kommt bei Zeile 96 (CellText := Kunde.GetText(Column);) ein Fehler, sobald ich auf [Kunde neu] klicke. Weil er das Objekt noch nicht hat?
Ja, ich habe jetzt mal "Trash Variables" aktiviert. Im OnInitNode Event kann die Klasseninstanz noch gar nicht existieren. Wenn man
in der Prozedur vstInitNode den Vergleich auf nil herausnimmt, funktioniert es.
Der TVirtualStringTree reserviert zwar den Speicher für die Daten, initialisiert diesen aber nicht.

Code: Alles auswählen

procedure TForm1.vstInitNode(Sender: TBaseVirtualTree; ParentNode,
  Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
begin
  TCustomer(vst.GetNodeData(Node)^) := TCustomer.Create;
end;
Ich bin nicht ganz so fit in der Standardvorgehensweise beim VirtualTree.
Bei mir ist immer ein Pointer an der Node dran und mir war die Vorgehensweise zu umständlich.

Aus dem Grund habe ich für mich ein paar kleine Änderungen an dem Tree vorgenommen.

1. Die Definition von TVirtualNode habe ich erweitert:

Code: Alles auswählen

 type
  TVirtualNode = record
   ...
    LastChild: PVirtualNode; // link to the node's last child...
    Daten : Pointer;
    Data: record end;        // this is a placeholder, each node gets extra data determined by NodeDataSize
  end;
D.h. jede Node hat immer einen Pointer Daten.

Dann habe ich in der Deklaration von TBaseVirtualTree hinzugefügt.

Code: Alles auswählen

    function AddChildObject(Parent: PVirtualNode; UserData: Pointer): PVirtualNode; virtual;
und das so implementiert.

Code: Alles auswählen

function TBaseVirtualTree.AddChildObject(Parent: PVirtualNode; UserData: Pointer): PVirtualNode;
begin
  Result := AddChild(Parent);
  Result.Daten := UserData;
end;
Dadurch vereinfachen sich alle Events die irgendwie eine Node übergeben.
Das fängt bei mir immer so an (z.B. GetText)

Code: Alles auswählen

procedure TForm1.vstGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
begin
  if (Assigned(Node)) and
    (Assigned(Node.Daten)) then
      CellText := TMyBaseClass(Node.Daten).GetText(Column);
end;

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

Re: Fragen zu TVirtualStringTree

Beitrag von wp_xyz »

fliegermichl hat geschrieben:
Sa 13. Jan 2024, 18:07
1. Die Definition von TVirtualNode habe ich erweitert:

Code: Alles auswählen

 type
  TVirtualNode = record
   ...
    LastChild: PVirtualNode; // link to the node's last child...
    Daten : Pointer;
    Data: record end;        // this is a placeholder, each node gets extra data determined by NodeDataSize
  end;
D.h. jede Node hat immer einen Pointer Daten.
Verstehe ich jetzt nicht. Es gibt doch schon den Data Record - der ist zu deiner Verfügung, da kommen die Daten des Nodes rein. VST.GetNodeData(node) liefert die Adress des Elements TVirtualNode.Data zurück. Nichts hindert dich, statt eines Records dort einen Pointer oder eine Klasse einzusetzen, sofern den Tree gesagt wird, dass NodeDataSize gleich der Größe eines Pointers ist. Nur ist dann der Zugriff etwas ungewöhnlich, weil man den Pointer auf die Node-Daten zuerst dereferenzieren muss, um die Daten zu erhalten:

Code: Alles auswählen

type
  TTestData = class
    Number: Integer;
    Text1: String;
    Text2: String;
  end;

  TNodeData = TTestData;
  PNodeData = ^TNodeData; 
  
procedure TForm1.VirtualStringTree1GetText(Sender: TBaseVirtualTree; 
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; 
  var CellText: String);
var
  P: PNodeData;
  data: TTestData;
begin
  P := Sender.GetNodeData(Node);
  data := TTestData(P^);
  case Column of
    0: CellText := IntToStr(data.Number);
    1: CellText := data.Text1;
    2: CellText := data.Text2;
  end;
end;     
Siehe beigefügtes Projekt.
Dateianhänge
VST_Class.zip
(2.68 KiB) 46-mal heruntergeladen

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1436
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Fragen zu TVirtualStringTree

Beitrag von fliegermichl »

Ja aber genau darum ging es mir doch. Wenn ich TTreeNode.Data verwenden möchte, dann muß ich dem Tree sagen, daß meine Daten einen Pointer groß sind und ich muß bei jedem Zugriff Tree.GetNodeData verwenden.

Das war mir zu umständlich. Durch daß ich ein Feld Daten hinzugefügt habe, steht mir das immer einfach so zur Verfügung und ich muß das nicht immer erst mit Tree.GetNodeData holen. Tree.NodeDataSize bleibt bei mir immer -1.

Jeder Event der mir eine Node übergibt, übergibt damit automatisch meinen Daten Pointer auf den ich direkt zugreifen kann.

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

Re: Fragen zu TVirtualStringTree

Beitrag von wp_xyz »

Ist schon klar, aber du musstest dafür TVirtualNode verändern. Ich überblicke die Folgen der Änderung nicht, wahrscheinlich hast du Glück, und es ändert sich außer der Typdeklaration gar nichts. Aber bei sowas bin ich immer extrem vorsichtig, und das kommt für mich nur dann in Frage, wenn es keine andere Lösung für ein Problem gibt..

Benutzeravatar
Jim Knopf
Beiträge: 98
Registriert: So 18. Mai 2014, 15:16
OS, Lazarus, FPC: Win10
CPU-Target: 64Bit
Wohnort: Klagenfurt
Kontaktdaten:

Re: Fragen zu TVirtualStringTree

Beitrag von Jim Knopf »

Ich glaube nicht, dass die Recordgröße irgendwo als fixe Konstante verwendet wird, also glaube ich auch nicht, dass die Erweiterung Probleme macht. Um sicherzugehen müsste man natürlich alle Vorkommen checken.

Ich habe einen ähnlichen Gedanken. Die Idee geht dahin, 'Strings: TStrings' in den Record aufzunehmen, der exakt den Spalten entspricht und alle Inhalte als Strings enthält. In weiterer Folge bräuchte man nur die Stelle zu suchen, wo GetText aufgerufen wird, nachsehen, ob Strings zugewiesen ist und, wenn ja, die Methode GetText automatisch aus den Strings erledigen lassen. So müsste man nicht jedesmal mit der Case-Litanei von GetText arbeiten, sondern der Punkt entfiele einfach. Habe zwar noch nicht nachgesehen, aber es gibt sicher einen Event, wo man bei Bedarf den Text vor Anzeige adaptieren kann.

Was man damit ebenfalls lösen könnte ist das Speichern und Laden der VST-Daten. Beim Speichern würden alle Strings + der horizontale Node-Level in einen Stream gespeichert und beim Laden alles wieder von dort herausgeholt. Oder gibt es so etwas schon?

Mir kommt vor, dass VST viel Gewicht auf Virtualität legt, was natürlich bei Datenbanken mit großen Datenmengen sehr sinnvoll ist. Wenn man aber nur kleinere Mengen an Knoten brauch, meinetwegen 10.000, und die Daten fix sind, dann gefiele mir die beschriebene Methode besser. Übrigens handhabt das DevExpress so ähnlich und ich bin sehr gut damit gefahren.

Was haltet ihr von diesen Gedanken?

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1436
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Fragen zu TVirtualStringTree

Beitrag von fliegermichl »

Da eine Instanz von TStrings ja auch nur ein Pointer ist, funktioniert das ganz sicher.

Übrigens habe ich durch meine Erweiterung mit dem Feld Daten immer noch Data zur Verfügung. Kann bei Bedarf also auch mehrere Daten pro Node verwalten.

Benutzeravatar
Jim Knopf
Beiträge: 98
Registriert: So 18. Mai 2014, 15:16
OS, Lazarus, FPC: Win10
CPU-Target: 64Bit
Wohnort: Klagenfurt
Kontaktdaten:

Re: Fragen zu TVirtualStringTree

Beitrag von Jim Knopf »

Hallo wp_xyz,

habe mich nach deinem Beispiel (danke dafür!) gerichtet, aber es wird kein Knoten angelegt, was ich daran merke, dass er nach vt.GetText gar nicht hineinkommt. Ich komme mir vor wie behindert, hatte praktisch nie mit Zeigern arbeiten müssen. Was hab ich hier falsch gemacht?

Nach implementation:

Code: Alles auswählen

type
  PNodeStrings = ^TStringList;
Erzeugen

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var P: PNodeStrings;
    Strings: TStringList;
    Node: PVirtualNode;
    I: Integer;
    S: string;
begin
  if InputQuery('Titel bestimmen', 'Titel:', S) then
  begin
    Node := vt.AddChild(vt.FocusedNode);
    P := vt.GetNodeData(Node);
    Strings := TStringList.Create;
    for I := 0 to vt.Header.Columns.Count-1 do
      Strings.Add('');
    Strings[3] := S;
    P^ := Strings;
    vt.Expanded[vt.FocusedNode] := True;
  end;
end;


Könnte hier der Wurm drin liegen? Braucht der nur Pointerlänge?

Code: Alles auswählen

procedure TForm1.vtGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer);
begin
  NodeDataSize := SizeOf(PNodeStrings);
end; 
Die Wertezuweisung wäre ganz simpel - wenn er denn hineinkäme ...

Code: Alles auswählen

procedure TForm1.vtGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
var P: PNodeStrings;
    Strings: TStringList;
begin
  P := Sender.GetNodeData(Node);
  Strings := TStringList(P^);
  CellText := Strings[Column];
end;

Und dann hätte ich noch eine Zusatzfrage: Kann man irgendwie den Index der Spalten auslesen? Muss fast ein Trick sein, denn bei den Columns selbst habe ich nichts gefunden.

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

Re: Fragen zu TVirtualStringTree

Beitrag von wp_xyz »

Könntest du das in ein kleines Projekt packen? Wenn ich selbst versuche, das nachzuvollziehen, mache ich vielleicht etwa anders als du...
Jim Knopf hat geschrieben:
So 14. Jan 2024, 15:33
Kann man irgendwie den Index der Spalten auslesen? Muss fast ein Trick sein, denn bei den Columns selbst habe ich nichts gefunden.
Ist das nicht das Column-Argument, das dem Event-Handlers übergeben wird?

Antworten