Entwickler-Ecke
Grafische Benutzeroberflächen (VCL & FireMonkey) - TreeView laden und speichern
JanHH - Fr 28.02.03 04:48
Titel: TreeView laden und speichern
Hallo Forum,
treeview und kein Ende.. Einen solchen zu Laden und zu Speichern ist ja relativ einfach (TTreeView.SaveToFile bzw. -.LoafFromFile), aber was ist mit den damit verbundenen Daten (TTreeNode.Data)? Auch diese (ist bei mir jedeweils ein record bzw. ein ^record) zu laden und zu speichern ist kein Problem, nur werden doch die TreeNodes sequentiell in der Reihenfolge ihres Entstehens im Items-Array abgelegt, und die Data-Pointer deshalb logischerweise beim sequentiellen Speichern auch in dieser Reihenfolge.. wenn der Tree nun aber wieder geladen wird, geht die ursprüngliche Reihenfolge verloren, und das Items-Array wird neu angelegt, dabei halt umsortiert, und wenn ich die damit verbundenen Daten-Objekte dann einlese, ist die zuordnung nicht mehr korrekt.. und ich bin da ziemlich ratlos wie man das "einfach" lösen kann. Die Delphi-Hilfe erzählt da irgendwas von BLOB-Streams, was ist das??
Weiss irgend jemand Rat?
Danke und Gruß,
Jan
maximus - Fr 28.02.03 15:28
Hi.
Ich sehe zwei Möglichkeiten:
1. die property 'ItemID' des nodes in deinem record speicher und nachher wieder zuordnen. Musst aber testen ob sie auch gleich bleibt.
2. Main favorit: Eine klasse von TTreeNode ableiten und ein zusätzliches published property definieren. zB. 'RecordIndex'. Vor dem speichern allen nodes das record index geben. Nach dem laden alle nodes durchgehen und das richtige record linken!
mfg maximus.
JanHH - Fr 28.02.03 15:50
2. klingt gut, danke!
Gruß
Jan
JanHH - Fr 28.02.03 23:37
Hmm.. funktioniert doch nicht, glaube ich, ohne es ausprobiert zu haben. DENN: Wenn ich die treeview mittels SaveToFile/-Stream speichere, wird er ja nur als text gespeichert.. und die ID wird nicht mitgespeichert, also vermutlich auch durchaus geändert manchmal, und wenn ich zum Baum-Zusammenbauen ursprünglic nicht TTreeNode, sondern was davon abgeleitetes benutzt haben, dann wird das ja beim lesen nicht wieder so gemacht, sondern es werden nur die normalen TreeNode-Objekte genommen, und die Bezüge sind wieder alle weg.. Ich müsste also eher gleich die ganze TTreeView-Klasse ableiten und sowohl die TreeNodes durch eigene ersetzten, als auch die LoadFromFile-Methode durch ne eigenen ersetzen. oder? *ächz*
Die einfachste, wenn auch unelegante, Methode, das Problem zu lösen, ist meines erachtens nach, den Bezug zwischen Data-Record und TreeNode über die TreeNode-Caption herzustellen, welche in meinem Anwendungsfall eindeutig identifizierend ist.
Hat jemand spontan Inspriation zu meinen Ausführungen? Oder hab ich irgendwas übersehen oder falsch verstanden?
Danke und Gruß,
Jan
wulfskin - Sa 01.03.03 15:18
Hallo JanHH!
Es hat ne Weile gedauert, aber ich glaube es klappt jetzt:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90:
| type TNodeType = (ntNew, ntChild, ntNormal);
procedure SaveTree(const FileName: String; const Nodes: TTreeNodes); procedure WriteString(const FS: TFileStream; const S: String); var L: Word; begin L := Length(S); FS.Write(L, SizeOf(L)); FS.Write(S[1], L); end;
var I: Integer; Node, Parent: TTreeNode; FS: TFileStream; NodeType: TNodeType; Rec: PRecord; begin FS := TFileStream.Create(FileName, fmCreate or fmShareExclusive); with FS do begin try Parent := nil; I := Nodes.Count; Write(I, SizeOf(I)); for I := 0 to Nodes.Count - 1 do begin Node := Nodes[I]; if Node.Parent = nil then NodeType := ntNew else if Node.Parent <> Parent then NodeType := ntChild else NodeType := ntNormal; WriteString(FS, Node.Text); Rec := PRecord(Node.Data); Write(Rec, SizeOf(PRecord)); Write(NodeType, SizeOf(NodeType)); Parent := Node.Parent; end; finally Free; end; end; end;
procedure LoadTree(const FileName: String; const Nodes: TTreeNodes); function ReadString(const FS: TFileStream): String; var L: Word; S: String; begin S := ''; FS.Read(L, SizeOf(L)); SetLength(S, L); FS.Read(PChar(S)^, L); Result := S; end;
var I: Integer; FS: TFileStream; Rec: PRecord; S: String; NodeType: TNodeType; Node: TTreeNode; begin FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); with FS do begin try Node := nil; Read(I, SizeOf(I)); for I := (I - 1) downto 0 do begin S := ReadString(FS); Read(Rec, SizeOf(PRecord)); Read(NodeType, SizeOf(NodeType)); case NodeType of ntNew: Node := Nodes.AddObject(nil, S, Rec); ntChild: Node := Nodes.AddChildObject(Node, S, Rec); ntNormal: Node := Nodes.AddObject(Node, S, Rec); end; end; finally Free; end; end; end; |
Den Typ PRecord musst du halt auf deinen Record anpassen. Ich muss gestehen, dass er bei mir noch die Daten falsch läd.
Ich konnte den Fehler bis jetzt nocht nicht finden, vielleicht findest du ihn.
Probier es einfach mal bei dir aus, vielleicht klappt es ja bei dir!
Gruß Hape!
Moderiert von
Klabautermann: Code durch Delphi-Tags ersetzt.
JanHH - Sa 01.03.03 20:05
Hallo,
vielen Dank für die Mühe, aber funktioniert das wirklich!? Kann mir gar nicht vorstellen, das der Baum auf diese Weise korrekt gespeichert wird. Du speicherst ja nur die Information, OB ein Node Child- oder Parentnode ist, nicht, von WEM, wenn ich das richtig sehe.. also, die Bezüge der nodes aufeinander gehen verloren!? Würde ich zumindest vermuten.. Dein Ansatz ist ja, den Baum (bzw. die einzelnen Knoten) abzuklappern und quasi Binär zu speichern.. Hmm, sollte ich vielleicht auch mal ausprobieren. Danke also für die Mühe und Inspiration! Zumindest das zuordnen des Data-Records über die Caption ist als "Notlösung" ja auf jeden Fall möglich.. gar nicht so tirival, so ein TreeView, sehe ich das richtig?
Grüße
Jan
wulfskin - Sa 01.03.03 20:15
Hallo JanHH!
So funktioniert es tatsächlich. Das (oder der?) Node muss gar nicht wissen wer das Parent ist, denn dass ergibt sich zwangsweise aus der Reihenfolge!
Deshalb probier es erstmal aus, bevor du urteilst, denn es funktioniert ;)!
Das einzige, was beim mir nicht geklappt hat, war das Laden der Records. Das dürfte aber für dich kein großes Problem sein das zu beheben. Die Reihenfolge hat immer gestimmt und wird auch immer stimmen!
Gruß wulfskin!
JanHH - Sa 01.03.03 20:47
Hallo,
meine Vermutung ist halt, das die Reihenfolge zwar anfangs "OK" ist, aber wenn man Knoten hin- und herverschiebt (property Owner) sich die Parent-Child-Beziehungen verändern, aber ja nicht der Listenplatz, den ein Node im Items-Array hat, und alles durcheinandergerät.. Oder?
Gruß
Jan
wulfskin - Sa 01.03.03 20:55
JanHH hat folgendes geschrieben: |
Hallo,
meine Vermutung ist halt, das die Reihenfolge zwar anfangs "OK" ist, aber wenn man Knoten hin- und herverschiebt (property Owner) sich die Parent-Child-Beziehungen verändern, aber ja nicht der Listenplatz, den ein Node im Items-Array hat, und alles durcheinandergerät.. Oder?
Gruß
Jan |
Sorry, blick ich net! Der Aufbau wird so geladen, wie er davor war. Versuch es doch einfach mal, dann kannst du ja immer noch meckern!
Gruß wulfskin!
JanHH - Sa 01.03.03 21:05
Naja die Reihenfolge der Knoten im Items-Array enstpricht nicht unbedingt der, wie sie in der Baumdarstellung sind (also von oben nach unten, so ganz visuell *g*), da man sie ja mit drag&drop hin-und-herschieben kann (in meiner Applikation). Und dabei ändert sich ja soweit ich weiss nicht die Reihenfolge im Items-Array, sonder nur die Owner-Property. Daher bezweifle ich das das so geht. Bei einem Baum, der nicht "editierbar" ist, sicher, aber wenn man darin was verschoben hat.. und ich meckere auch gar nicht, sondern denke lediglich drüber nach, mittlerweile reichlich frustriert ;).
Gruß und Danke,
Jan
wulfskin - Sa 01.03.03 21:19
JanHH hat folgendes geschrieben: |
Naja die Reihenfolge der Knoten im Items-Array enstpricht nicht unbedingt der, wie sie in der Baumdarstellung sind (also von oben nach unten, so ganz visuell *g*), da man sie ja mit drag&drop hin-und-herschieben kann (in meiner Applikation). Und dabei ändert sich ja soweit ich weiss nicht die Reihenfolge im Items-Array, sonder nur die Owner-Property. Daher bezweifle ich das das so geht. Bei einem Baum, der nicht "editierbar" ist, sicher, aber wenn man darin was verschoben hat.. und ich meckere auch gar nicht, sondern denke lediglich drüber nach, mittlerweile reichlich frustriert ;).
Gruß und Danke,
Jan |
Ahhh, jetzt versteh ich!
Wenn das so ist, wie du sagst (habe es noch getestet), dann geht es natürlich nicht so, wie ich das gemacht habe.
Dann müsste man den Baum durchgehen. Mit GetFirstNode hat man ja ein Anfang und man kann sich durch die Childs durchhangeln. Das einzige Problem das ich dann noch sehe ist das nächste "oberste" Item zu bekommen! Wenn du weisst, wie man an das kommt, dürfte das ganze kein Problem sein!
Gruß wulfskin!
JanHH - Sa 01.03.03 21:35
Tja ist wohl so.. ich werd das mal machen und den code dann posten, können sicher viele leute gebrauchen ;) Den Baum rekursiv durchzugehen ist ja prinzipiell kein problem, und in die datei müssen dann halt push- und pop-markierungen geschrieben werden.. nervig, aber möglich.
Naja und die andere Möglichkeit ist halt die Verknüpfung über die Caption des Nodes.. doof aber funzt sofern diese eindeutig ist.
Ich werds mal testen, melde mich dann wieder..
Gruß
Jan
JanHH - So 02.03.03 04:31
es funktioniert :-).. habe ein neues thema draus gemacht.
Moderiert von
Tino: Beitrag aus dem neuen Topic hier eingefügt.
Hallo,
der folgende Code speichert und lädt einen TTreeView INKLUSIVE der Data-Records, die mit den Nodes verknüpft sind.. vielleicht interessiert das jemanden ;).
WriteInt, WriteString, ReadInt und ReadString sind Hilfsfunktionen zum Laden und Speichern selbiger Daten, habe die wegen der Trivialität weggelassen, und WriteNodeData bzw. ReadNodeData speichern und laden den Data-Record (bzw. pRecord).
Sorry für die Formatierung, wie fügt man sourcecode hier "richtig" ein? Bin irgendwie planlos was das angeht..
Grüße
Jan
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
| procedure TForm1.WriteTreeView(f: TFileStream); begin WriteInt(f, 0); WriteTreeNode(TreeView1.Items[0], f); WriteInt(f, 99); end;
procedure TForm1.WriteTreeNode(node: TTreeNode; f: TFileStream); var myNode: TTreeNode; begin WriteString(f, node.Text); WriteNodeData(f, node.Data);
myNode := node.getFirstChild; if assigned(myNode) then begin WriteInt(f, 1); WriteTreeNode(myNode, f); end;
myNode := node.GetNextSibling; if assigned(myNode) then begin WriteInt(f, 0); WriteTreeNode(myNode, f); end else begin WriteInt(f, 2); end; end;
procedure TForm1.LoadTreeView(f: TFileStream; tree: TTreeView); var node: TTreeNode; begin node := nil; tree.Items.Clear; LoadTreeNode(f, tree, node); end;
procedure TForm1.LoadTreeNode(f: TFileStream; tree: TTreeView; node: TTreeNode); var s: String; nodeType: Integer; myNode, myParentNode: TTreeNode; begin myParentNode := node; nodeType := -1; while not(nodeType=99) do begin case nodeType of 0: begin ReadString(f, s); myNode := tree.Items.AddChild(myParentNode, s); myNode.Data := ReadNodeData(f); end; 1: begin ReadString(f, s); myParentNode := myNode; myNode := tree.Items.AddChild(myParentNode, s); myNode.Data := ReadNodeData(f); end; 2: begin if assigned(myParentNode) then myParentNode := myParentNode.Parent; end; end; ReadInt(f, nodeType); end; end; |
Moderiert von
Tino: Code-Tags hinzugefügt.
Moderiert von
Klabautermann: Code durch Delphi-Tags ersetzt.
JanHH - Mo 03.03.03 17:03
Hmm, das laden funktioniert doch noch nicht, die Data-Records und die Knoten werden nicht richtig zugeordnet.. seltsam *frust*.
Gibts nicht doch irgendwo sowas wie DIE trivial-Lösung dafür?
Jan
JanHH - Mo 03.03.03 17:24
Es geht doch, der Bug war woanders in meinem Programm, der obige Code ist also RICHTIG.. Sorry. und sorry für mein konfuses geposte..
Jan
Dr. Phil - Do 26.08.04 00:11
Hallo!
Sorry, dass ich so ein altes Thema wieder hier rauf hole, aber von Streams hab ich herzlich wenig Ahnung.
Ich würde gern wissen wie die Hilfsfunktionen WriteInt, WriteString, ReadInt und ReadString ausschauen könnten, ich finde die garnicht so trivial ;)
Danke im Voraus!
JanHH - Do 26.08.04 02:34
So hab ich das gelöst.
Gruß
Jan
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72:
| unit MyFiles;
interface
uses Classes, SysUtils;
procedure WriteInt(var f: TFileStream; i: Integer); procedure ReadInt(f: TFileStream; var i: Integer);
procedure WriteReal(var f: TFileStream; r: real); procedure ReadReal(f: TFileStream; var r: real);
procedure WriteString(var f: TFileStream; s: String); procedure ReadString(f: TFileStream; var s: String);
procedure WriteBoolean(var f: TFileStream; b: Boolean); procedure ReadBoolean(f: TFileStream; var b: Boolean);
implementation
procedure WriteInt(var f: TFileStream; i: Integer); begin f.WriteBuffer(i, SizeOf(Integer)); end;
procedure WriteReal(var f: TFileStream; r: real); begin f.WriteBuffer(r, SizeOf(real)); end;
procedure WriteString(var f: TFileStream; s: String); var l: Integer; begin l := Length(s); f.WriteBuffer(l,SizeOf(Integer)); f.WriteBuffer(PChar(s)^,l); end;
procedure WriteBoolean(var f: TFileStream; b: Boolean); begin f.WriteBuffer(b, SizeOf(Boolean)); end;
procedure ReadInt(f: TFileStream; var i: Integer); begin f.ReadBuffer(i, SizeOf(Integer)); end;
procedure ReadReal(f: TFileStream; var r: real); begin f.ReadBuffer(r, SizeOf(real)); end;
procedure ReadString(f: TFileStream; var s: String); var p: PChar; l: Integer; begin f.ReadBuffer(l,SizeOf(Integer)); p := AllocMem(l*2); f.ReadBuffer(p^,l); s := String(p); FreeMem(p); end;
procedure ReadBoolean(f: TFileStream; var b: Boolean); begin f.ReadBuffer(b, SizeOf(Boolean)); end;
end. |
Moderiert von
Klabautermann: Code durch Delphi-Tags ersetzt.
Dr. Phil - Do 26.08.04 10:19
Danke!
Und was ist mit WriteNodeData und ReadNodeData?
Ich möchte das Beispiel komplett übernehmen, also wär das angenehm wenn du mir zeigen könntest wie die prozeduren bei dir aussehen.
Ach und super, dass sich jemand die Arbeit dafür gemacht hat!
maximus - Do 26.08.04 10:59
JanHH hat folgendes geschrieben: |
So hab ich das gelöst.
...
|
Das wäre nicht unbedingt nötig gewesen. Genau die selben funktionen bieten die stream-hifsklassen TReader und TWriter. Und noch vieles mehr.
Ich sag das nur damit damit andere sich das etvl. zu nutze machen können, nicht um diene unit zu kritisieren.
JanHH - Do 26.08.04 15:40
Ich glaub es gibt ne ganze Menge Hilfsfunktionen in Delphi, von denen man nicht unbedingt was weiss und die man redundanterweise dann selber nachprogrammiert.
Ich kann mich an den ganzen TreeView-Kram gar nicht mehr so genau erinnern und hab das mittlerweile eh verworfen (da ein treeview-speichern unnötig ist in einem vernünftig entworfenen Programm, da das speichern von daten direkt im treeview designmässig ziemlicher Müll ist, allerdings mag es bei eher kleinen Programmen ok sein), aber das Lesen und Schreiben der NodeData-Objekte hängt natürlich vom Objekt an sich ab, insofern kann man das kaum generell beantworten. Am besten, man hat einen Typ TNodeData, ein NodeData ist halt ein myNodeData: TNodeData, und die Klasse hat eine Funktion Read und Write.
Gruß
Jan
maximus - Do 26.08.04 16:50
JanHH hat folgendes geschrieben: |
...Am besten, man hat einen Typ TNodeData, ein NodeData ist halt ein myNodeData: TNodeData, und die Klasse hat eine Funktion Read und Write.
Gruß
Jan |
Geanau und diesen methoden übergibt man am besten einen TReader oder TWriter, die zuvor mit dem ziel-stream initialisiert wurden. Dann hat man perfekten service, OOP konform und wenig ärger. Und hast natürlich recht damit, das man den treeView selbst nicht speichern sollte. Wenn man nach MVC (Model-View-Control schicht modell) arbeitet, dann bietet es sich ja an, dass man nur das Model in die persistenz 'schiebt' :D
JanHH - Do 26.08.04 16:56
Jo hast recht. Ich hab neulich mal ein Programm richtig designt, mit MVC und undo-stack usw., und finde, dass Delphi eigentlich eher weniger geeignet dafür ist.. Delphi ist zwar supersimpel und einfach, aber das begünstigt meiner Ansicht nach gerade eine unsaubere Arbeitsweise, z.B. Daten direkt in Steuerelementen speichern (dann wirds mit Dokument laden/speichern nämlich echt konfus) usw.
Gruß
Jan
maximus - Mi 01.09.04 11:38
JanHH hat folgendes geschrieben: |
Jo hast recht. Ich hab neulich mal ein Programm richtig designt, mit MVC und undo-stack usw., und finde, dass Delphi eigentlich eher weniger geeignet dafür ist.. Delphi ist zwar supersimpel und einfach, aber das begünstigt meiner Ansicht nach gerade eine unsaubere Arbeitsweise, z.B. Daten direkt in Steuerelementen speichern (dann wirds mit Dokument laden/speichern nämlich echt konfus) usw.
... |
Ja das ist in der tat ein problem. Delphi fördert quick&dirty implementierungen und Änfänger sind da besonders gefährdet, da man ja alles so schön in die form-klasse schreiben kann. Die reue kommt dann wenn sie so derartig gross wird, das man sie kaum noch warten kann. Wenn man bedenkt, dass die forma-klasse eigentlich nur als Mediator konzipiert ist, der zwischen allen 'subsystemen' vermittelt, dann wird einem auch klar das man zusätliche funktionalität besser in logische units auslagert.
Imo könnte delphi da mehr für tun, dass anfängern der weg zu ordentlichem design erleichtert wird. Ich hab auch viele verbrechen in der hinsicht begangen, bevor ich gemerkt hab wie man den Form-klassen-spagetti entgeht.
grüsse!
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!