VirtualStringTree Nodes aufbauen

Rund um die LCL und andere Komponenten
Antworten
Ronny58
Beiträge: 88
Registriert: So 27. Apr 2014, 20:35

VirtualStringTree Nodes aufbauen

Beitrag von Ronny58 »

Per Drag und Drop ziehe ich mir die Bezeichnung einer Struktur bestehend aus Spalten und Zeilen aus einer Listbox auf meine VST.
Die Procedure CreateVstSM(sRow: String; sCol: String) wird beim Drop ausgeführt und soll mir die Zeilen und Spalten in der VST aufbauen.
In den Parameter sRow und sCol stehen jeweils die Dateiname von Listen, die die Struktur von VST beschreiben.
sRow für die Nodes, sCol für die Spalten.
Diese beiden Dateien werden in zwei TList geladen, die CreateVstSM für den Aufbau der VST benutzt.

Alle Daten sind da. Parameter gefüllt, beide TList geladen und auch die Nodes werden durchlaufen/aufgebaut.
Keine Fehlermeldung/Abbrüche beim durchlauf der Procedure. Es sollte in einem Rutsch funktionieren.

Und doch muss ich den CreateVstSM immer zweimal ausführen (Droppen), um die Zeilen und Spalten komplett aufzubauen.
1. Drop: CreateVstSM erzeugt mir nur die Spalten, obwohl er auch durch die Nodes läuft.
2. Drop: CreateVstSM baut mir nun auch die Zeilen (Nodes) dazu auf.

Was übersehe ich?

(Anmerkung: Groups: TStringArray ist nur in Vorbereitung und wird im erst zweiten Schritt gefüllt. Hier kommen dann die Berechtigung rein: WRITE)

Code: Alles auswählen

Type
  PSMData = ^TSMData;
  TSMData = record
     Key_Vater   : String;        { Key Vater-Element                                 }
     Key_Kind    : String;        { Key Kind-Element                                  }
     Bezeichnung : String;        { Bezeichnung Kind-Element                          }
     Groups      : TStringArray;  { Array für Berechtigungen                          }
  end;

...

Procedure TfMain.CreateVstSM(sRow: String; sCol: String);
var i, iCol : Integer;
    vtc : TVirtualTreeColumn;
    Data : PSMData;
    XNode: PVirtualNode;
    sVater, sKind, sBezeichnung : String;
begin
  vstSM.Clear;
  vstSM.Header.Columns.Clear;

  lGroups.LoadFromStream(sCol);
  lGroups.SortBy(tleBezeichnunmgAsc);

  lElements.LoadFromStream(sRow);
  lElements.SortBy(tleLevelAsc);
  PaintSmPanel(pSM,'-1','-1');

  vstSM.BeginUpdate;

  { erstmal alle Spalten in der VST erzeugen }
  for i := 0 to lGroups.Count do vtc := vstSM.Header.Columns.Add;

  // erste Spalte erhält die Überschrift der Nodes. Beispiel: Kostenarten-Hierarchie (sRow)
  vstSM.Header.Columns[0].Style := vsText;
  vstSM.Header.Columns[0].Options := [coAllowClick,coAllowFocus,coEnabled,coParentBidiMode,coResizable,coSmartResize,coShowDropMark,coVisible];
  vstSM.Header.Columns[0].Width:=100;
  vstSM.Header.Columns[0].Text := sRow;
  vstSM.Header.Columns[0].Tag  := i;

  // in alle weiteren Spalten werden die Benutzergruppen angelegt. Benutzergruppen (sCol)
  for i := 0 to lGroups.Count-1 do
   begin
     vstSM.Header.Columns[i+1].Style := vsText;
     vstSM.Header.Columns[i+1].Options := [coAllowClick,coAllowFocus,coEnabled,coParentBidiMode,coResizable,coSmartResize,coShowDropMark,coVisible];
     vstSM.Header.Columns[i+1].Width:=100;
     vstSM.Header.Columns[i+1].Text := lGroups.Items[i].Name_Kind;
     vstSM.Header.Columns[i+1].Tag  := i;
   end;
   vstSM.Header.Options := [hoAutoResize,hoColumnResize,hoDblClickResize,hoVisible];
   vstSM.Refresh;

   // jetzt die Hierarchie in der ersten Spalte aufbauen: Bsp.: Kostenarten-Hierarchie
   for i := 0 to lElements.Count - 1 do
   begin
     pb.Position:=i;
     sVater       := lElements.Items[i].Key_Vater;
     sKind        := lElements.Items[i].Key_Kind;
     sBezeichnung := lElements.Items[i].Bezeichnung;
     if sBezeichnung = '' then sBezeichnung := sKind;
     if sVater <> sKind then
     begin
       XNode := vstSMFindNode(sVater,vstSM.GetFirst);
       XNode := vstSM.Addchild(XNode);
     end
     else
       XNode:=vstSM.AddChild(nil);
     if xNode <> NIL then
     begin
       Data := vstSM.GetNodeData(XNode);
       vstSM.ValidateNode(XNode,false);
       if Assigned(Data) then
       begin
          Data^.Bezeichnung := sBezeichnung;
          Data^.Key_Vater   := sVater;
          Data^.Key_Kind    := sKind;
          setLength(Data^.Groups,lGroups.Count);
          for iCol := 0 to lGroups.Count do
          begin
          end;
       end;
     end;
   end;
   vstSM.EndUpdate;
   vstSM.Refresh;
end;

Function  TfMain.vstSMFindNode(s : String;StartNode: PVirtualNode): PVirtualNode;
var XNode: PVirtualNode;
    Data:  PSMData;
begin
  XNode:= StartNode;
  while XNode <> nil do
  begin
    Data := vstSM.GetNodeData(XNode);
    if Data^.Key_Kind = s then Break;
    xNode := vstSM.GetNext(XNode);
  end;
  result := XNode
end;

procedure TfMain.vstSMInitNode(Sender: TBaseVirtualTree; ParentNode,
  Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var Data : PSMData;
begin
  Data := vstSM.GetNodeData(Node);
  Data^.Key_Kind:='';
  Data^.Key_Vater:='';
  Data^.Bezeichnung:='';
  setLength(Data^.Groups,lGroups.Count);
  FillChar(Data^.Groups,SizeOf(Data^.Groups),0);
end;

procedure TfMain.vstSMGetNodeDataSize(Sender: TBaseVirtualTree;
  var NodeDataSize: Integer);
begin
  vstSM.NodeDataSize:=SizeOf(TSMData);
end;

procedure TfMain.vstSMFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var Data : PSMData;
begin
  Data := vstSM.GetNodeData(Node);
  if Assigned(Data) then
  begin
    FillChar(Data^,Sizeof(TSMData),0);
    SetLength(Data^.Groups,0);
  end;
end;

procedure TfMain.vstSMGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
var Data: PSMData;
begin
  Data := vstSM.GetNodeData(Node);
  if Column = 0 then
    CellText := Data^.Bezeichnung;
//  else
//    CellText := Data^.Groups[Column];  (noch nicht implementiert)
end;


[/codeVS. Sie erzeugt Zeilen und Spalten in der VST (so geplant).
Die Parameter sRow für die Zeilen und sCol für die Spalten werden korrekt an CreateVstSM über geben und die Prozedur wird wie geplant durchlaufen. 
Alle Daten sind da, keine Fehlermeldung, es sollte in einem Rutsch funktionieren.

[u]Leider muss ich den CreateVstSM immer zweimal ausführen, um die Zeilen und Spalten komplett aufzubauen.[/u]
1. Drop: CreateVstSM erzeugt mir [b]nur [/b]die Spalten, obwohl er auch durch die Nodes läuft.
2. Drop: CreateVstSM baut mir nun auch die Zeilen (Nodes) dazu auf.

CreateVstSM wird bei jedem Aufruf vollständig korrekt durchlaufen (inkl. Nodes). Alle Daten in den TList sind auch beim ersten Durchlauf da. Trotzdem werden die Nodes erst beim zweiten Durchlauf erzeugt.

Was übersehe ich? 

(Anmerkung: Groups: TStringArray ist nur in Vorbereitung und wird im erst zweiten Schritt gefüllt. Hier kommen dann die Berechtigung rein: WRITE)


[code]
Type
  PSMData = ^TSMData;
  TSMData = record
     Key_Vater   : String;        { Key Vater-Element                                 }
     Key_Kind    : String;        { Key Kind-Element                                  }
     Bezeichnung : String;        { Bezeichnung Kind-Element                          }
     Groups      : TStringArray;  { Array für Berechtigungen                          }
  end;

...

Procedure TfMain.CreateVstSM(sRow: String; sCol: String);
var i, iCol : Integer;
    vtc : TVirtualTreeColumn;
    Data : PSMData;
    XNode: PVirtualNode;
    sVater, sKind, sBezeichnung : String;
begin
  vstSM.Clear;
  vstSM.Header.Columns.Clear;

  lElements.LoadFromStream(sRow);
  lElements.SortBy(tleLevelAsc);
  PaintSmPanel(pSM,'-1','-1');

  vstSM.BeginUpdate;

  { erstmal alle Spalten in der VST erzeugen }
  for i := 0 to lGroups.Count do vtc := vstSM.Header.Columns.Add;

  // erste Spalte erhält die Überschrift der Nodes. Beispiel: Kostenarten-Hierarchie (sRow)
  vstSM.Header.Columns[0].Style := vsText;
  vstSM.Header.Columns[0].Options := [coAllowClick,coAllowFocus,coEnabled,coParentBidiMode,coResizable,coSmartResize,coShowDropMark,coVisible];
  vstSM.Header.Columns[0].Width:=100;
  vstSM.Header.Columns[0].Text := sRow;
  vstSM.Header.Columns[0].Tag  := i;

  // in alle weiteren Spalten werden die Benutzergruppen angelegt. Benutzergruppen (sCol)
  for i := 0 to lGroups.Count-1 do
   begin
     vstSM.Header.Columns[i+1].Style := vsText;
     vstSM.Header.Columns[i+1].Options := [coAllowClick,coAllowFocus,coEnabled,coParentBidiMode,coResizable,coSmartResize,coShowDropMark,coVisible];
     vstSM.Header.Columns[i+1].Width:=100;
     vstSM.Header.Columns[i+1].Text := lGroups.Items[i].Name_Kind;
     vstSM.Header.Columns[i+1].Tag  := i;
   end;
   vstSM.Header.Options := [hoAutoResize,hoColumnResize,hoDblClickResize,hoVisible];
   vstSM.Refresh;

   // jetzt die Hierarchie in der ersten Spalte aufbauen: Bsp.: Kostenarten-Hierarchie
   for i := 0 to lElements.Count - 1 do
   begin
     pb.Position:=i;
     sVater       := lElements.Items[i].Key_Vater;
     sKind        := lElements.Items[i].Key_Kind;
     sBezeichnung := lElements.Items[i].Bezeichnung;
     if sBezeichnung = '' then sBezeichnung := sKind;
     if sVater <> sKind then
     begin
       XNode := vstSMFindNode(sVater,vstSM.GetFirst);
       XNode := vstSM.Addchild(XNode);
     end
     else
       XNode:=vstSM.AddChild(nil);
     if xNode <> NIL then
     begin
       Data := vstSM.GetNodeData(XNode);
       vstSM.ValidateNode(XNode,false);
       if Assigned(Data) then
       begin
          Data^.Bezeichnung := sBezeichnung;
          Data^.Key_Vater   := sVater;
          Data^.Key_Kind    := sKind;
          setLength(Data^.Groups,lGroups.Count);
          for iCol := 0 to lGroups.Count do
          begin
          end;
       end;
     end;
   end;
   vstSM.EndUpdate;
   vstSM.Refresh;
end;

Function  TfMain.vstSMFindNode(s : String;StartNode: PVirtualNode): PVirtualNode;
var XNode: PVirtualNode;
    Data:  PSMData;
begin
  XNode:= StartNode;
  while XNode <> nil do
  begin
    Data := vstSM.GetNodeData(XNode);
    if Data^.Key_Kind = s then Break;
    xNode := vstSM.GetNext(XNode);
  end;
  result := XNode
end;

procedure TfMain.vstSMInitNode(Sender: TBaseVirtualTree; ParentNode,
  Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var Data : PSMData;
begin
  Data := vstSM.GetNodeData(Node);
  Data^.Key_Kind:='';
  Data^.Key_Vater:='';
  Data^.Bezeichnung:='';
  setLength(Data^.Groups,lGroups.Count);
  FillChar(Data^.Groups,SizeOf(Data^.Groups),0);
end;

procedure TfMain.vstSMGetNodeDataSize(Sender: TBaseVirtualTree;
  var NodeDataSize: Integer);
begin
  vstSM.NodeDataSize:=SizeOf(TSMData);
end;

procedure TfMain.vstSMFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var Data : PSMData;
begin
  Data := vstSM.GetNodeData(Node);
  if Assigned(Data) then
  begin
    FillChar(Data^,Sizeof(TSMData),0);
    SetLength(Data^.Groups,0);
  end;
end;

procedure TfMain.vstSMGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
var Data: PSMData;
begin
  Data := vstSM.GetNodeData(Node);
  if Column = 0 then
    CellText := Data^.Bezeichnung;
//  else
//    CellText := Data^.Groups[Column];  (noch nicht implementiert)
end;



Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 845
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Winux (L 2.0.11 FPC 3.2)
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: VirtualStringTree Nodes aufbauen

Beitrag von fliegermichl »

Pack das ganze doch mal in ein zip File damit man das nachvollziehen kann.

Ronny58
Beiträge: 88
Registriert: So 27. Apr 2014, 20:35

Re: VirtualStringTree Nodes aufbauen

Beitrag von Ronny58 »

Hab noch meine Probleme das so Programm so einzudampfen, das ich es hochladen kann. Original 10 MB gezippt ohne exe.
Habe es gerad kaputt abgespeckt. Glücklicherweise eine Kopie.

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 845
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Winux (L 2.0.11 FPC 3.2)
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: VirtualStringTree Nodes aufbauen

Beitrag von fliegermichl »

Unter Projekt -> Projekt veröffentlichen exportiert Lazarus alles was nötig ist und nur das.

Ronny58
Beiträge: 88
Registriert: So 27. Apr 2014, 20:35

Re: VirtualStringTree Nodes aufbauen

Beitrag von Ronny58 »

Ich habe es jetzt klein bekommen und kann es hochladen.
Im DragDrop habe ich den zweiten Aufruf von "CreateVstSM" aus kommentiert.
Jetzt muss man also zweimal Droppen um den vollständige Aufbau zu erhalten.

Betriebssystem Windows 10, zwei Lazarus-Installationen:

32-Bit-Trunc:
Lazarus-Version: 2.0.6
Datum: 2021.03.12
FPC-Version 3.0.4

64-Bit-Trunc:
Lazarus-Version: 2.0.12
Datum: 2021.02.21
FPC-Version 3.2.0

In beiden Lazarus-Installation der selbe Effekt.
Dateianhänge
Berechtigung.zip
(256.93 KiB) 14-mal heruntergeladen

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 845
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Winux (L 2.0.11 FPC 3.2)
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: VirtualStringTree Nodes aufbauen

Beitrag von fliegermichl »

Kann ich nicht nachvollziehen.
Man kann einmal, zweimal oder dreimal droppen ohne das sich was ändert.
FPC 3.3.1 Laz Trunk

Ronny58
Beiträge: 88
Registriert: So 27. Apr 2014, 20:35

Re: VirtualStringTree Nodes aufbauen

Beitrag von Ronny58 »

bekommst du auch Nodes angezeigt oder jedes mal nur Spalten?

Ich bekomme beim 1. Drop die Spalten und beim 2. Drop zu den Spalten zusätzlich die Nodes.

Also zb_Leistungsstelle auf die VST droppen.

jus
Beiträge: 48
Registriert: Fr 6. Mai 2011, 13:29

Re: VirtualStringTree Nodes aufbauen

Beitrag von jus »

Hallo,

habe mal paar grobe Bugs beseitigt, die mir auf die Schnelle aufgefallen sind. Schaue bitte mal, ob du damit weiter kommst. Habe dir die Version mit den geringfügigen Änderungen hier angehängt.
Berechtigung.zip
(273.5 KiB) 11-mal heruntergeladen
Das erste was mir aufgefallen ist, ist dass du die NodeDataSize, nicht gleich bei Programmstart definierst. Ich habe die Nodegrössendefinition von OnGetNodeDataSize Event in das FormCreate verschoben. Damit mußte bei mir deine CreateVstSM Methode nicht mehr 2x aufgerufen werden um die Unterebenen von den Nodes aufzubauen.
Weiters ist mir aufgefallen, dass du einerseits die Nodes mit der Add Methode hinzufügst und andererseits auch Sachen in OnInitNode Event stehen hast. Eigentlich wird der OnInitNode Event von der VST meist dazu verwendet die Nodes automatisch aufbauen zu lassen, indem man einfach die NodeCount Anzahl einstellt und in OnInitNode Event wird der Node mit Daten befüllt. Da du ja mit der Add Methode die einzelnen Nodes hinzufügst, brauchst du eigentlich das OnInitNode Event nicht wirklich.
Außerdem ist mir aufgefallen, dass das Programm abschmiert, wenn man die VST bis ganz nach rechts scrollt. Das deutet meist auf Fehler bei OnGetText Event hin. Ich habe in OnGetText Event paar Sicherheitsabfragen eingebaut. Jetzt schmiert es zumindest bei mir nicht mehr ab.

lg,
jus

Ronny58
Beiträge: 88
Registriert: So 27. Apr 2014, 20:35

Re: VirtualStringTree Nodes aufbauen

Beitrag von Ronny58 »

Hallo Jus,

vielen Dank für Deine Hilfe.

Ich habe schon einige kleine Tools für meine Arbeit in der Firma geschrieben, 99,9% unter Verwendung von VST. Zuletzt Programme für unsere Planungs-Datenbank TM1 zur Strukturpflege, zur Analyse und Verwaltung von Lade-Prozessen und jetzt für eine komfortable Pflege von Planungsberechtigungen. Das Angebot von TM1 zu diesen Themen ist sehr strümpfig.
In nicht einem meiner bisherigen Programm hatte ich die OnNodeDataSize im Form.Create gebraucht. Trotzdem funktionieren alle Programme tadellos und werden bei uns ständig genutzt. Auch in den Tutorials zu VST habe ich sowas nie gelesen. Ich wäre nie auf die Idee gekommen so das Problem zu lösen, obwohl ich immer den Verdacht hatte das mein Problem mit der NodeDataSize etwas zu tun haben musste.
In dem aktuellen Programm habe ich noch eine zweite VST im Einsatz, die ebenfalls problemlos ohne OnNodeDataSize im Form.Create funktioniert. Also es bleiben da immer noch Fragezeichen bei mir.

Daher auch das OnInitNode. Das war hier jetzt eine reine Verzweiflungstat, mit der ich gehofft hatte mein Node-Problem zu lösen. Das schmeiße ich wieder raus.

Und danke für den OnGetText-Hinweis. Auf den Fehler wäre ich auch noch gestoßen im weiteren Test-Verlauf.

LG Ronny

Antworten