Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Delphi Fragen zu VirtualTreeView (https://www.delphipraxis.net/213831-fragen-zu-virtualtreeview.html)

H.Bothur 2. Okt 2023 23:12

Fragen zu VirtualTreeView
 
Moin,

ich arbeite das Tutorial zu Virtualtreeview durch und habe schon auf den ersten Seiten zwei Fragen:

1) auf Seite zwei wird geschrieben wie man an einer bestimmten Stelle einen Node einfügen kann:

Delphi-Quellcode:
Node := vst.InsertNode(vst.FocusedNode, amInsertAfter);
allerdings wird bei mir das "amInsertAfter" als unbekannt markiert - fehlt mir da ein uses ?


2) auf Seite 3 werden Noites mit Beschriftung erstellt:

Delphi-Quellcode:
function AddVSTStructure(AVST: TCustomVirtualStringTree; ANode: PVirtualNode;
 ARecord: TTreeData): PVirtualNode;
var
  Data: PTreeData;
begin
  Result:=AVST.AddChild(ANode);
  Data:=AVST.GetNodeData(Result);
  Avst.ValidateNode(Result, False);
  Data^.FCaption:=ARecord.FCaption;
end;
procedure TForm1.Button3Click(Sender: TObject);
var
  I: Integer;
  TreeData: TTreeData;
begin
  VST.NodeDataSize:=SizeOf(TTreeData);
  VST.BeginUpdate;
  for I:=0 to 100 do
  begin
    TreeData.FCaption:='Node-Nummer: '+IntToStr(I);
    AddVSTStructure(VST,nil,TreeData);
  end;
  VST.EndUpdate;
end;
Ich hötte erwartet das jetzt statt "Node" immer "Node-Nummer 1..2..3..4..usw" steht - es steht aber weiterhin nur Node.
Denke ich da falsch ? Was mache ich denn wenn da nicht nur Node stehen soll.´??

Hans

DieDolly 2. Okt 2023 23:37

AW: Fragen zu VirtualTreeView
 
amInsertAfter war in der Unit VirtualTrees.pas.
Mit Version 7.6, die du scheinbar nutzt, gab es Breaking Changes. Deswegen ist amInsertAfter jetzt in VirtualTrees.Types.pas.

jaenicke 3. Okt 2023 06:04

AW: Fragen zu VirtualTreeView
 
Zitat:

Zitat von H.Bothur (Beitrag 1527618)
Ich hötte erwartet das jetzt statt "Node" immer "Node-Nummer 1..2..3..4..usw" steht - es steht aber weiterhin nur Node.
Denke ich da falsch ? Was mache ich denn wenn da nicht nur Node stehen soll.´??

Du zeigst nur den Teil, in der diese Information in die Datenstruktur geschrieben wird. Fehlt vielleicht das OnGetText Event? Dort bekommt der Tree von dir die Information welcher Text in der angegebenen Zeile und Spalte angezeigt werden soll.

H.Bothur 3. Okt 2023 11:01

AW: Fragen zu VirtualTreeView
 
Zitat:

Zitat von DieDolly (Beitrag 1527619)
amInsertAfter war in der Unit VirtualTrees.pas.
Mit Version 7.6, die du scheinbar nutzt, gab es Breaking Changes. Deswegen ist amInsertAfter jetzt in VirtualTrees.Types.pas.

Danke !

Zitat:

Zitat von jaenicke (Beitrag 1527620)
Zitat:

Zitat von H.Bothur (Beitrag 1527618)
Ich hötte erwartet das jetzt statt "Node" immer "Node-Nummer 1..2..3..4..usw" steht - es steht aber weiterhin nur Node.
Denke ich da falsch ? Was mache ich denn wenn da nicht nur Node stehen soll.´??

Du zeigst nur den Teil, in der diese Information in die Datenstruktur geschrieben wird. Fehlt vielleicht das OnGetText Event? Dort bekommt der Tree von dir die Information welcher Text in der angegebenen Zeile und Spalte angezeigt werden soll.

OK - ich hätte geacht das beim zeichnen des Baums diese Daten mit angezeigt werden :-) ... aber ich arbeite mal weiter - wird wohl noch kommen :-)

Hans

jaenicke 3. Okt 2023 11:37

AW: Fragen zu VirtualTreeView
 
Zitat:

Zitat von H.Bothur (Beitrag 1527627)
OK - ich hätte geacht das beim zeichnen des Baums diese Daten mit angezeigt werden :-)

Du selbst hast ja die Datenstruktur außerhalb der Komponente definiert. Daher kennt die Komponente auch deren Struktur nicht und kann damit auch nichts anfangen. Daher musst du der Komponente die Daten daraus geben, wenn diese sie zur Anzeige braucht (OnGetText) und die Komponente sagt dir Bescheid, wenn der Benutzer diese im Baum verändert hat (OnSetText), damit du sie in deine Datenstruktur schreiben kannst.

DieDolly 3. Okt 2023 11:37

AW: Fragen zu VirtualTreeView
 
Zitat:

OK - ich hätte geacht das beim zeichnen des Baums diese Daten mit angezeigt werden ... aber ich arbeite mal weiter - wird wohl noch kommen
Wenn ich du wäre, würde ich das ganze AddVSTStructure-Ding wegwerfen. So habe ich das auch gemacht und es ist totaler Mist. Diese Tutorials sollten verboten werden.

Speichere deine Daten in einer ObjectList oder sowas Klassenstruktur. Damit lässt es sich wesentlich besser arbeiten. Das ist anfänglich mehr Aufwand, aber es lohnt sich.

mytbo 3. Okt 2023 14:30

AW: Fragen zu VirtualTreeView
 
Zitat:

Zitat von H.Bothur (Beitrag 1527627)
OK - ich hätte geacht das beim zeichnen des Baums diese Daten mit angezeigt werden :-)

Ein Beispiel für die Umsetzung findest du in diesem Artikel mit Quelltext.

Bis bald...
Thomas

jaenicke 3. Okt 2023 18:46

AW: Fragen zu VirtualTreeView
 
Zitat:

Zitat von DieDolly (Beitrag 1527629)
Wenn ich du wäre, würde ich das ganze AddVSTStructure-Ding wegwerfen. So habe ich das auch gemacht und es ist totaler Mist. Diese Tutorials sollten verboten werden.

Mittlerweile geht das auch generisch, so dass die Pointerspielerei außen wegfällt. Grundsätzlich kommt es auf den Anwendungsfall an, ob das Sinn macht. Für viele Zwecke macht die Vorgehensweise durchaus Sinn.

H.Bothur 5. Okt 2023 12:51

AW: Fragen zu VirtualTreeView
 
Leider habe ich noch eine Frage da ich etwas noch nicht verstehe - geht um das Speichern der Daten. Ich habe noch nie mit Streams gearbeitet, deswegen stehe ich da ein bisschen auf dem Schlauch.

Zum Speichern wird folgende procedure genutzt:

Delphi-Quellcode:
procedure TForm1.vstSaveNode(Sender: TBaseVirtualTree; Node: PVirtualNode;
 Stream: TStream);
var
  Data: PTreeData;
  Len: integer;
begin
  Data := vst.GetNodeData(Node);
  Len := Length(Data.TestStr);
  Stream.write(Len, SizeOf(Len));
  Stream.write(PChar(Data.TestStr)^, Len);
end;
Da sind die Daten aber nur ein String, wie geht das denn wenn es mehrere Strings sind ? So etwa ? Das kommt mir kompliziert vor:

Delphi-Quellcode:
procedure TSerienDB.VSTSaveNode(Sender: TBaseVirtualTree; Node: PVirtualNode; Stream: TStream);
var
  Data: PTreeData;
  Len: integer;
begin
  Data := VST.GetNodeData(Node);
  Len := Length(Data.FCaption);
  Stream.write(Len, SizeOf(Len));
  Stream.write(PChar(Data.FCaption)^, Len);
  Len := Length(Data.FColumn1);
  Stream.write(Len, SizeOf(Len));
  Stream.write(PChar(Data.FColumn1)^, Len);
  Len := Length(Data.FColumn2);
  Stream.write(Len, SizeOf(Len));
  Stream.write(PChar(Data.FColumn2)^, Len);
  Len := Length(Data.FColumn3);
  Stream.write(Len, SizeOf(Len));
  Stream.write(PChar(Data.FColumn3)^, Len);
end;
Wie ist das denn richtig ?

Gruss
Hans

himitsu 5. Okt 2023 13:13

AW: Fragen zu VirtualTreeView
 
Zitat:

PChar und Length
NEIN :!:

Delphi-Quellcode:
Len := Length(S) * SizeOf(S[1]);
// oder
Len := Length(S) * SizeOf(Char);
Denn PChar = PWideChar
und WideChar = 2 Byte (seit Delphi 2009, also seit 15 Jahren schon)

mytbo 5. Okt 2023 14:16

AW: Fragen zu VirtualTreeView
 
Zitat:

Zitat von H.Bothur (Beitrag 1527741)
Wie ist das denn richtig ?

Warum möchtest du die Daten auf diese Weise speichern? Wenn ich mich recht erinnere, willst du das Datenverzeichnis auf der Festplatte im Virtual TreeView abbilden. Die Verwendung der Speicher- und Ladefunktion des TreeViews macht in der Regel nur bei statischen Daten oder bei einer Trennung zwischen Daten definierter Struktur und Aufbereitung/Anordnung Sinn. Bei Ersteren ist es eher eine pragmatische Lösung. Ein Beispiel für den zweiten Fall wäre ein Fragenkatalog zur Beantwortung. Hier trennt man Fragen und/oder visuelle Präsentation von den Antworten jedes Teilnehmers. In vielen Fällen ist der Aufbau des TreeViews direkt aus den Daten, die Datenstruktur liefert Daten und Abbildung, die bessere Lösung.

Bis bald...
Thomas

H.Bothur 5. Okt 2023 15:01

AW: Fragen zu VirtualTreeView
 
Moin

das kann sein, da habe ich einfach den Code aus dem Tutorial kopiert.
Was mich irritiert ist das ich jedes Feld aus dem Record separat speichern muss.
Kann man da den Record nicht en bloc speichern?

Hans

H.Bothur 5. Okt 2023 15:28

AW: Fragen zu VirtualTreeView
 
Hallo Thomas,

mir ging es um die einfachste Methode die Datei zu speichern.
Es handelt sich um Fernsehserien, sprich

Serie
Folge
Wie oft abgespielt

Das ist relativ statisch, da werden die Daten eben nur selten geändert.
Und da suche ich halt den bequemsten Weg.

Hans

Gausi 5. Okt 2023 16:03

AW: Fragen zu VirtualTreeView
 
Wenn man "Fernsehserien" durch "Alben" ersetzt und "Folgen" durch "Lied", hat man das Grundkonzept von meinem mp3-Player. :stupid:

Da habe mir ich zum Speichern ein eigenes Dateiformat gebastelt, welches auch nicht angepasst werden muss, wenn später mal weitere Informationen gespeichert werden sollen. Zusätzlich zur Länge und den eigentlichen String-Inhalt kommt bei mir u.a. noch eine internere "ID" hinzu, die angibt, welche Art von Info nun als nächstes kommt. Z.B. 1: Name der Serie, 2: Name der Folge, 3: Dateiname der Datei, 4: Größe der Datei, etc.pp. Dann können später weitere Datenfelder hinzugefügt werden, ohne dass ich das Dateiformat ändern muss (zwecks Abwärtskompatibilität beim Update des Programms). Das Ende der Daten für ein Objekt wird bei mir über die ID "255" gekennzeichnet.

Gespeichert werden bei mir nur die Informationen zu den einzelnen Titeln (bei dir: Episoden). Die Gruppierung nach Alben (Serien) geschieht bei mir erst zur Laufzeit beim Laden der Liste. Dann ist auch leicht eine alternative Gruppierung im Treeview möglich (nach Erscheinungsjahr, Genre, Dauer, wasweißich).

Das, was dir zu kompliziert vorkommt, ist also vermutlich der sinnvollere Weg. "Problem" sind halt die Strings mit variabler Länge. ;-)

Nachteil bei meinem Ansatz ist nur, dass selbst kleinste Änderungen an einem einzelnen Objekt (Abspielzähler +1) ein Neuschreiben der gesamten Speicherdatei benötigen. Ein punktgenaues Ändern einzelner Stellen in der Datei ist nicht vorgesehen.

mytbo 5. Okt 2023 17:09

AW: Fragen zu VirtualTreeView
 
Zitat:

Zitat von H.Bothur (Beitrag 1527750)
mir ging es um die einfachste Methode die Datei zu speichern.

Wenn es ganz einfach sein soll:
Delphi-Quellcode:
uses
  mormot.core.base,
  mormot.core.json,
  mormot.core.os;
 
type
  TSeriesItem = packed record
    FileName: String;
    SeriesName: String;
    EpisodeName: String;
    PlaybackCount: Integer;
  end;

  TSeriesItems = array of TSeriesItem;

  TSeriesDB = record
    Items: TSeriesItems;
    function Load(const pmcFileName: String): Boolean;
    procedure Save(const pmcFileName: String);
  end;
 
function TSeriesDB.Load(const pmcFileName: String): Boolean;
var
  json: RawJson;
begin
  Result := False;
  json := StringFromFile(pmcFileName);
  if json <> '' then
    Result := (DynArrayLoadJson(Items, Pointer(json), TypeInfo(TSeriesItems)) <> Nil);
end;

procedure TSeriesDB.Save(const pmcFileName: String);
var
  json: RawJson;
begin
  json := DynArraySaveJson(Items, TypeInfo(TSeriesItems));
  if json <> '' then
    FileFromString(json, pmcFileName);
end;
Im TreeView Knoten "Data" auf ein TSeriesItem zeigen lassen oder den Daten-Index speichern. JSON als DB-Format. Das Serialisieren mit mORMot ist tolerant. Du kannst jeder Zeit dem Daten-Record TSeriesItem Felder hinzufügen oder sie wieder entfernen.

Oder klassisch mit Hilfe von TDynArray umgesetzt:
Delphi-Quellcode:
type
  TSeriesDB = class(TObject)
  private
    FList: TDynArray;
    FItems: TSeriesItems;
    FItemCount: Integer;
    function GetItem(pmIndex: Integer): PSeriesItem;
  public
    constructor Create;
    destructor Destroy; override;
    function Add(const pmcFileName: String; const pmcSeriesName, pmcEpisodeName: String; pmPlaybackCount: Integer = 0): Integer;
    function Delete(pmIndex: Integer): Boolean;
    function LoadFromJsonFile(const pmcFileName: String): Boolean;
    function SaveToJsonFile(const pmcFileName: String): Boolean;
    property Item[pmIndex: Integer]: PSeriesItem
      read GetItem;
    property ItemCount: Integer
      read FItemCount;
  end;

constructor TSeriesDB.Create;
begin
  inherited Create;
  FList.Init(TypeInfo(TSeriesItems), FItems, @FItemCount);
end;

destructor TSeriesDB.Destroy;
begin
  FList.Clear;
  inherited Destroy;
end;

function TSeriesDB.GetItem(pmIndex: Integer): PSeriesItem;
begin
  if (pmIndex >= 0)
    and (pmIndex < FItemCount) then
  begin
    Result := @FItems[pmIndex];
  end
  else
    Result := Nil;
end;

function TSeriesDB.Add(const pmcFileName, pmcSeriesName, pmcEpisodeName: String; pmPlaybackCount: Integer): Integer;
var
  item: PSeriesItem;
begin
  item := FList.NewPtr;
  item.FileName := pmcFileName;
  item.SeriesName := pmcSeriesName;
  item.EpisodeName := pmcEpisodeName;
  item.PlaybackCount := pmPlaybackCount;
  Result := FItemCount - 1;
end;

function TSeriesDB.Delete(pmIndex: Integer): Boolean;
begin
  Result := FList.Delete(pmIndex);
end;

function TSeriesDB.LoadFromJsonFile(const pmcFileName: String): Boolean;
var
  json: RawJson;
begin
  json := StringFromFile(pmcFileName);
  if json <> '' then
    Result := (FList.LoadFromJson(Pointer(json), Nil, Nil, True) <> Nil)
  else
    Result := False;
end;

function TSeriesDB.SaveToJsonFile(const pmcFileName: String): Boolean;
var
  json: RawJson;
begin
  json := FList.SaveToJson(True, jsonHumanReadable);
  if json <> '' then
    Result := FileFromString(json, pmcFileName)
  else
    Result := False;
end;
Anwendung so:
Delphi-Quellcode:
procedure ...DoGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  item: PSeriesItem;
begin
  item := FSeriesDB.Item[Node.Index];
  if item <> Nil then
  begin
    case Column of
      0: CellText := item.FileName;
      1: CellText := item.SeriesName;
      2: CellText := item.EpisodeName;
      3: Celltext := item.PlaybackCount.ToString;
    end;
  end;
end;

begin
  FSeriesDB := TSeriesDB.Create;
  FSeriesDB.Add('X.mp4', 'X-Men', 'Folge 2');
  FSeriesDB.Add('Y.mp4', 'Akte-Y', 'Folge 456');
  VirtualStringTree.OnGetText := DoGetText;
  VirtualStringTree.RootNodeCount := FSeriesDB.ItemCount;
Bei Verwendung als Grid ohne filternde Funktionen reicht der Zugriff über Node Index auf die Daten aus.

Bis bald...
Thomas

DieDolly 5. Okt 2023 17:38

AW: Fragen zu VirtualTreeView
 
Zitat:

Ich habe noch nie mit Streams gearbeitet, deswegen stehe ich da ein bisschen auf dem Schlauch.
Deswegen lass das mit dem AddStructure oder was du da machst sein.

Speichere deine Daten in einer Klassenstruktur und einer generischen Liste, dann brauchst du keine Streams.


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:14 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