Entwickler-Ecke
Datenbanken - DBTreeView ?!? bzw. Treeview speichern
cyberax - Sa 10.02.07 19:31
Titel: DBTreeView ?!? bzw. Treeview speichern
Hallo,
benutze mysql und Zeos Komponenten.
ich habe eine Artikel tabelle. Nun sollen die Artikel verschiedenen Kategorien zugeordnet werden. Dafür ist sicher ein Treeview passend.
Die Artikel sollen dann so dargestellt werden.
+Hauptgruppe 1
-Hauptgruppe 2
+Untergruppe 1
+Untergruppe 2
-Untergruppe 3
- Artikel 1
- Artikel X
....
Es sollen beliebig viele Gruppen und Untergruppen bzw. Einträge definiert werden können.
Ich dachte mir das so, wie es in vielen Online - Shops der Fall ist.
Wie kann ich nun am besten hinterlegen/speichern wo welcher Artikel liegt, bzw. wie der Baum aussieht?
Bei dem Treeview kann man ja einfach den Inhalt speichern, aber ich möchte nicht den dargestellten Text speichern, sondern etwas eindeutiges - wie die ID des Artikels.
Gibt es für so etwas eine Komponente? Oder wie könnte ich das am einfachsten machen?
Gruß cyberax
Moderiert von
Christian S.: Titel geändert.
stifflersmom - Sa 10.02.07 20:15
Ich weiß nicht ob es die beste Komponente für Dein Vorhaben ist,
aber was Bäume angeht ist wohl das VirtualTreeView von Mike Lischke
die Komponente überhaupt. Deer Umgang damit ist zwar definitiv aufwendiger
als mit einem "normalen" TreeView, dsfür hast Du allerdings dievesre Möglichkeiten
Deine Anforderungen zu befriedigen.
Beim VirtualTreeView kannst Du (ja mußt Du sogar) jedem Node einen record zuordnen
und der kann natürlich auch eine ID aus Deiner Tabelle aufnehmen.
By The way, hast Du vor, eine Verwaltung für die datenbank vom Oscommerce oder sogar
Xtcommerce zu entwickeln?
Moin
Delete - Sa 10.02.07 21:16
dafür baut man sich normal eine rekursive tabelle auf. dort stehen dann einfach die zuordnungen drin. wenn du möchtest, kannst ja zusätzliche informationen hinterlegen. tabellenaufbau ist so
t{@key; SubKey; ...}
die musst dann nur noch auswerten und deinen tree entsprechend aufbauen. sollte kein problem sein.
HelgeLange - Sa 10.02.07 21:16
man kann bei dem "normalen" TTreeView auch Daten zuordnen, jeder Knoten hat einen Data-pointer ;)
Was Du brauchst, ist nicht das speichern eines Treeviews, sondern eine vernünftige Daten-Definition in deiner Datenbank.
zum bsp:
Table1=Hauptgruppe1
ID : integer; (unique!)
Name : String;
... (was du sonst noch brauchst)
Table2=Untergruppe
ID : Integer; (unique!)
ID_Hauptgruppe : Integer; (Verweis auf die ID der Hauptgruppe)
Name : String;
... (was du sonst noch brauchst)
Table3=Artikel
ID : Integer; (unique!)
ID_Untegruppe: Integer; (Verweis auf die ID der Untergruppe, damit hast du automatisch die Hauptgruppe dazu)
Name : String;
...
somit hast du für alle artikel den Baum.
Im programm erzeugst du für alle Hauptgruppen einen Knoten, dann Lädst du die Untergruppen und legst Kindknoten in den Hauptgruppen an für die untergruppen. Dann gehst zu deinen Artikeln und machst das gleiche dort, allerdings als kinder der untergruppen. Aus der Tabelle weisst Du ja, zu welche Untergruupe welcher Artikel gehört.
Du kannst es natürlich auch so machen, dass Du statt 2 Tabellen für Gruppen nur 1 machst, die noch ein ID feld bekommmt, wo Du auf die Hauptgruppe verweist. Ist es 0, dann ist der Gruppeneintrag eine Hauptgruppe. Ist es grösser als 0, dann eine UNtergruppe und die ID ist der verweis auf die Hauptgruppe.
cyberax - Do 15.02.07 23:21
Also vielen Dank für eure Hilfe!
@stifflersmom - habe das mit der Komponente auf die "Schnelle" nicht hinbekommen. Habe nur die für Lazarus gefunden.. :roll: ..
werde es nun so machen, wie ihr es mir geraten habt. Eine tabelle
"gruppe" mit:
id
gr_id
name
In der Tabelle "artikel" gibt es eine "gr_id" wo der Verweis auf die nächste Gruppe steht. In der nächsten Gruppe steht der Verweis per "gr_id" auf die nächste und so weiter, bis die "gr_id" = 0 ist. Das wäre dann eine Hauptgruppe.
Also vielen Dank. Muss es nun nur noch umsetzen :D
cyberax - Fr 16.02.07 00:45
| Zitat: |
die musst dann nur noch auswerten und deinen tree entsprechend aufbauen. sollte kein problem sein
|
...stellt doch ein kleines Problem dar. Habe schon im Forum gesucht aber leider nix gefunden, was mit hilft. Also ich habe nun eine tablle "gruppe" wie beschrieben.
Ich hatte mir das mit dem Füllen des Trees so gedacht, dass ich aus der tabelle erst alle Einträge abfrage, die zur Hauptgruppe gehören ("gr_id"=0) und diese in das Tree schreibe. dann wollte ich die nächsten einlesen und so weiter. Nur habe ich keine Ahnung, wie ich das am besten umsetze bzw scheint mir das recht umständlich.. :roll:
@Grenzgaenger: meintest du, ich solle in die artikel tabelle ein Feld haben in dem der vollständige Pfad steht (zum Beispiel:"Hauptgruppe1/Untergruppe8/Artikelx")?
Delete - Fr 16.02.07 01:00
also das ist 'n rekursiver algo. musst mal bei deiner datenbank schaun, vielleicht unterstützt sie das ja per sql. :-) . bpw. oracle konnte zumindest in version 7 entsprechende auflösungen in sql direkt auflösen. bei mySQL kenn ich mich gar nicht aus. ansonsten musst ihn halt per software nachbilden.
der ist eigentlich recht einfach, musst nur aufpassen, dass sich der algo durch sich selbst definiert. und er macht ja da auch nichts anderes wie sich den knoten herauszuhohlen, sich selbst einzuknüpfen und zu gucken ob er unterknoten hat, wenn ja, fängt das ganze wieder von vorne an... bis in die 'zig'ste ebene.. wichtig dabei ist, dass die tabellendefiniton auch rekursiv aufgebaut ist, wie in meinen ersten post aufgezeigt,
sonst brichst du dir die finger bei der implemtation.
wünsch dir noch viel erfolg.
ich meinte, in der tabelle sollten die primärschlüssel stehen. die tabelle selbst braucht nur aus zwei feldern zu bestehen, einen schlüssel und einen schlüssel, der auf den schlüssel verweisst. bspw.
t{@k;@s}
Quelltext
1: 2: 3: 4: 5: 6:
| 1 3 3 4 3 5 5 6 5 7 7 9 |
das würde den folgenden baum ergeben
UGrohne - Fr 16.02.07 15:17
Ich hatte dasselbe Problem auch schon mal, allerdings mit C++ und Firebird.
In
diesem Artikel [
http://www.delphi-forum.de/viewtopic.php?p=96763#96763] steht meine damalige Lösung dazu, mit einer Stored Procedure, die die Daten entsprechend aufbereitet und einer Prozedur, die das TreeView füllt. Ich denke, es sollte nicht so schwierig sein, beides in Delphi-code zu übersetzen und noch die Verwendung der Data-Eigenschaft einzubinden.
cyberax - Sa 17.02.07 13:34
Also ich habe nun die tabelle "gruppe" mal wie folgt gefüllt.
Quelltext
1: 2: 3: 4: 5: 6:
| id gr_id name 1 0 Autos 2 0 Fahrräder 3 1 Verbrenner 4 1 Elektro 5 3 1:8 |
wollte also im Tree folgendes Bild:
Quelltext
1: 2: 3: 4: 5:
| - Autos - Verbrenner - 1:8 - Elektro - Fahrräder |
In Delphi habe ich folgenden Code (sehr einfach gestrikt):
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
| var MyTreeNode2: TTreeNode; begin tv_eg1.Items.Clear; with ZQuery_GRUPPE do begin sql.Text:='SELECT * FROM GRUPPE WHERE GR_ID = 0'; open; end; ZQuery_GRUPPE.First; repeat tv_eg1.Items.AddFirst( nil,ZQuery_GRUPPE.FieldByName('name').Text); until ZQuery_GRUPPE.FindNext=false;
with ZQuery_GRUPPE do begin sql.Text:='SELECT * FROM GRUPPE WHERE GR_ID > 0'; open; end; ZQuery_GRUPPE.First; repeat MyTreeNode2 := tv_eg1.Items[strtoint(ZQuery_GRUPPE.FieldByName('gr_id').Text)]; tv_eg1.Items.AddChild(mytreenode2,ZQuery_GRUPPE.FieldByName('name').Text); until ZQuery_GRUPPE.FindNext=false; |
Daraus ergibt sich leider folgendes Bild im Tree:
Quelltext
1: 2: 3: 4: 5:
| - Fahrräder - Autos - Verbrenner - Elektro - 1:8 |
Wieso das? Ich denk mal ich muss im Tree nach dem jeweiligen Node suchen und dann das entsprechende Child ranhängen. Aber wie kann ich nach dem Node suchen ohne dabei den Pfad anzugeben (z.B. "Autos/Verbrenner/1:8")? Gibt es für die Nodes im Tree auch eine "id" nach der ich im Tree suchen kann?
HelgeLange - Sa 17.02.07 15:58
Ich glaube, rein technisch gesehen musst Du nur die Abgfrage nach ID sortieren lassen, denn kein SubNode kann eher erstellt worden sein, als deren Parent.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| tv_eg1.Items.Clear; with ZQuery_GRUPPE do begin sql.Text:='SELECT * FROM GRUPPE ORDER BY ID ascending'; open; end; While not ZQuery_GRUPPE.EoF do begin If ZQuery_GRUPPE.FieldByName('id_gr').AsInteger = 0 Then tv_eg1.Items.AddFirst( nil,ZQuery_GRUPPE.FieldByName('name').Text) Else begin MyGr_ID := ZQuery_GRUPPE.FieldByName('id_gr').AsInteger; MyName := ZQuery_GRUPPE.FieldByName('name').Text; AddSubNodeMain(MyGr_ID, MyName); end; ZQuery_GRUPPE.Next; end; |
Deine Aufgabe wird es nun sein, eine
Delphi-Quelltext
1:
| procedure AddSubNodeMain(MyGr_ID : Integer; MyName: String); |
zu schreiben, die wieder eine rekursive funktion aufruft, welche immer tiefer in deinen Baum eindringt. Du gibst also dieser funktion einen deiner rootnodes mit (die, die gr_id=0 haben), die funktion testet, ob kinder da sind, wenn ja, schnappt sie sich das erste kind, ruft sich selbst auf mit dem ChildNode, wenn nicht, testet sie auf Siblings. Und bei jedem Node, der gefunden wird, wird geprüft, ob die Gr_ID des nodes die ist, die du suchst. Hast Du den node gefunden, gibst du ihn zurück als funktionsresult. Und wenn du in der funktion siehst, dass da ein Result ist (Result <> nil), dann immer schön die Suche abbrechen, dann kommst du aus der Rekursion raus.
Bist du nach dem ersten Durchlauf noch ohne Ergebnis, nimmst du ihn der procedure "AddSubNodeMain" den nächsten Sibling und schickst ihn wieder an die rekursive funktion.
Oh, und btw... wie du im Code ganz oben gesheen hast habe ich deine "Repeat..Until" gegen eine "While not EoF do.." getauscht. Das liegt daran, dass Repeat-Until fusslastige schleifen sind, While-Do sind aber kopflastige.
Eine Fusslastige schleife wird mind. 1x durchlaufen und wenn du aus irgendeinem Grund keine Daten drin hast, gibt es einen crash.
Bei einer kopflastigen Schleife kann das nicht passieren, da sie nicht unbedingt durchlaufen wird, sondern erst geprüft wird, ob eine gewissen Grundvoraussetzung gegeben ist (in dem Fall, ob überhaupt Daten für die abfrage zurück kamen).
Hoffe, das half dir jetzt ein bisschen, Klarheit reinzubringen in deinen Task. den rekursiven Algo hab ich dir bewusst nicht geschrieben, Du musst das ja lernen, nicht ich ;)
LG aus Caracas.
cyberax - Sa 17.02.07 17:46
Okay vielen Dank!
Muss also selbst überlegen ...?... :wink:
Okay ist richtig. Ich werde mich mal versuchen und das Resultat (auch wenn es unter Umständen zur allg. Belustigung beiträgt) :roll: später schreiben.
Grüße zurück aus Deutschland
cyberax - So 18.02.07 23:35
Bin mit dieser Sache "leicht" überfordert...
Habe nun versucht das ganze mal zu ordnen und denke das die
procedure AddSubNodeMain(MyGr_ID : Integer; MyName: String); wie folgt aufgebaut sein muss:
Quelltext
1: 2: 3:
| Node anlegen Kindertest durchfü0hren (where gr_id = id des akt. Nodes) bei Kindertest = JA ruft sich die Funktion selbst wieder auf, bei Kindertest = NEIN -> RAUS |
Sie Sieht so bei mit aus:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| procedure AddSubNodeMain(MyGr_ID : Integer; MyName: String); var MyTreeNode: TTreeNode; begin MyTreeNode := artikel_baum.tv_eg1.Items[mygr_id]; artikel_baum.tv_eg1.Items.AddChild(MyTreeNode,myname); if kindertest(artikel_baum.ZQuery_GRUPPE.FieldByName('id').AsInteger) = 1 then AddSubNodeMain(artikel_baum.zquery_search.FieldByName('gr_id').asinteger,artikel_baum.zquery_search.FieldByName('name').text); end; |
die Funktion "Kindertest" so:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
| function kindertest(id:integer):real; var MyTreeNode: TTreeNode; begin with artikel_baum.zquery_search do begin sql.Text:='SELECT * FROM GRUPPE WHERE ID = :searchid'; With Params.ParamByName('searchid') do begin DataType := ftinteger; Value := id; End; open; end; if artikel_baum.zquery_search.RecordCount>0 then result:=1 else result:=0; end; |
Funktioniert nicht. (Hätte mich auch gewundert. :D )
Stimmt denn mein Denkansatz wie oben beschrieben mit deinem überein bzw. könnte er so funktionieren ?
Benutze ja in der Kindersuch- Funktion auch ein zweites Query. Ist das nötig?
| Zitat: |
| Und bei jedem Node, der gefunden wird, wird geprüft, ob die Gr_ID des nodes die ist, die du suchst |
Ich habe keine Ahnung, wie ich das machen soll??!!!??
Habe mir echt den Kopf zerbrochen und alles auch mehrmals aufgemalt, aber komm hier nicht weiter :cry:
HelgeLange - Mo 19.02.07 01:01
Naja, du hattest nicht ganz richtig gelesen ;)
In der AddSubNodeMain gehst du "nur" zum root-knoten, der schnappt sich das erste child un dsendet es an eine ANDERE funktion, die rekursiv ist und sich selbst aufruft, je tiefer sie eindringt in den Baum.
AddSubNodeMain geht nur durch die Siblings (Geschwister) in der ersten ebene, die als Parent den Rootnode haben, ungefähr so :
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:
| unit uTreeView_Helper;
interface
uses Windows, DB, ComCtrls, Classes, SysUtils, Controls, GlobalVars, ResClient;
Type RNodeData = packed record StringID : Integer; HookNr : Integer; end; PNodeDate = ^RNodeData;
function TreeAddItem(Sender: TTreeView; IL: TImageList; pData : PConfigHookData; RC: TResClient; Resort: Boolean): TTreeNode; function TreeFindItem(Sender: TTreeView; NodeItem: TTreeNode; Name: String): TTreeNode;
implementation
function TreeAddItem(Sender: TTreeView; IL: TImageList; pData: PConfigHookData; RC: TResClient; Resort: Boolean): TTreeNode; var ThisNode, Node : TTreeNode; S : String; pND : PNodeDate; begin Node := nil; S := RC.GetString(pData^.ParentName); ThisNode := TreeFindItem(Sender, Node, S); If ThisNode <> nil Then Node := ThisNode Else begin New(pND); pND^.StringID := pData^.ParentName; pND^.HookNr := 0; Node := Sender.Items.AddChildObject(nil, S, pND); Node.ImageIndex := pData^.ParentImage; Node.SelectedIndex := pData^.ParentImage; end; New(pND); pND^.StringID := pData^.Name; pND^.HookNr := pData^.HookNr; S := RC.GetString(pData^.Name); ThisNode := Sender.Items.AddChildObject(Node, S, pND); ThisNode.ImageIndex := pData^.Image; ThisNode.SelectedIndex := pData^.Image; ThisNode.StateIndex := ThisNode.Level + 1; If Resort and (Node.Parent <> nil) Then Node.Parent.AlphaSort; Result := ThisNode; end; function TreeFindItem(Sender: TTreeView; NodeItem: TTreeNode; Name: String): TTreeNode; begin if NodeItem = nil Then NodeItem := Sender.Items.GetFirstNode Else NodeItem := NodeItem.GetFirstChild; if (NodeItem <> nil) and (NodeItem.Text <> Name) then repeat NodeItem := NodeItem.GetNextSibling; until (NodeItem = nil) or (NodeItem.Text = Name); Result := NodeItem; end; end. |
Ist ne Unit, die ich mal vor jahren geschrieben habe. In dem Fall ging ich davon aus, dass es immer nur 2 Lebel gibt in dieser Anwendung. Aber es sollte für dich ja kein Problem sein, es dementsprechend zu erweiterm. dass TreeFindItem bei Unterknoten sich entsprechend selbst wieder aufruft. Ist natürlich mehr Code, als Du brauchst, da es ich es für mich für eine ganz spezielle Situation geschrieben habe... Aber es sollte etwas helfen :)
Delete - Mo 19.02.07 02:36
hallo cyberax, wenn du
nur das ttreeview verwendest, kannste auch den baum über den index travisieren. das geht ähnlich wie bei 'er stringlist .
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| ... t: ttreeview; i: integer; ... begin for i := 0 to t.items.count - 1 do listbox.items.add(t.Items[i].Text); end. |
PS: aus der OH: "Der Zugriff auf Einträge im Baumdiagramm über den Index kann sehr zeitaufwendig sein, wenn das Diagramm viele Einträge enthält. Verwenden Sie daher aus Geschwindigkeitsgründen in Ihren Anwendungen möglichst wenige Indexzugriffe auf Elemente."
von daher ist die zugriffsmethode von helge immer noch dieser vorzuziehen. daher dies nur als anregung.
ausserdem solltest du beim hinzufügen die schlüsselinformation mit in die daten des nodes mit aufnehmen, dann kannst jederzeit auf deine datenbank eindeutig referenziern :-) . und nicht vergessen, beim löschen des nodes auch wieder die data strukturen freizugeben, sonst neigt sich dein hauptspeicher recht schnell dem ende zu ...
HelgeLange - Mo 19.02.07 07:09
mir ist grad 'ne Idee gekommen (die für meine Zwecke zu unpraktisch wäre/war, aber eben für Dich ganz gut). Ich habe das letzten Mal für meine Resourcen-Sachen in meinen Resourcen-Manager eingebaut.
Kurze beschreibung des Resourcen-Problems bei mir. Man kann in pro Bibliothek (also EXE oder DLL) Resourcen ID's nur bis MAX_WORD (also 0-65.535) vergeben. Nur will ich allerdings die Möglichkeit haben, alle Resourcen von n-DLL's (die Zahl weiss ich ja jetzt noch nicht und kann im laufe der Jahre anwachsen) in einem Manager zusammenfassen und dass das Modul, welches eine Resource anfordert, nicht wissen muss, woher die Resource kommt. Bei einer Beschränkung von 65k "kann" es mal voll werden. Als Lösung habe ich mir den Basis-Multiplikator ausgedacht. Das funktioniert wie ein Überlauf-Register. Sagen wir, die Haupt-Resourcen-DLL hat die Basis 0, also alle angeforderten Resourcen mit einer ID zw. 0 und 65.535 kommen aus dieser DLL. Ist die Nummer grösser als 65.535, wird eine Modulo-Operation angewandt, wie oft passt eine 65.535 in die ID, bevor das Endergebnis kleiner ist. Damit habe ich dann a) die wirkliche ID der Resource und b) die Basis, wonach ich die entsprechende Resourcen-Bibliothek anwählen kann, um dann die Resource zu holen.
Ok, jetzt wirst du dich fragen, was das ganze mit dir zu tun hat. Sehr viel :)
Ich finde Deine Idee nicht gut (und widerspricht auch allen gängigen designs, die ich kenne), die Artikel mit den Gruppen zusammen in einem Treeview unterzubringen (abhängig natürlich von der Anzahl der Artikel). Ich würde ab mind. 50 Artikel das Ganze eher 3-gegliedert aufbauen : TreeView, ListView, Detail-Ansicht (wahrscheinlich ein Panel mit aufgelegten Formular oder Controls). Aber das ist im Endeffekt Deine Sache :P
Was Du machen kannst, um schnell den richtigen Baum zu finden, ist ungefähr das Gleiche, was ich mit meinem Resourcen mache. Ich habe das Beispiel mit meinen Resourcen deswegen gewählt, weil es verständlicher im Grundprinzip ist. Und mit dem Grundprinzip kannst Du auch Dein Problem einfacher lösen.
Für jede Hauptgruppe vergibst Du einen Basis-Multiplaikator. Sagen wir 1.000.000. Ist eine beliebige gesuchte ID grösser als 1 mio, brauchst Du in das erste Element garnicht reinzuschauen, da es unter 1 mio ist. Damit scheidet ein ganzer Tree beim durchsuchen aus. In der Untergruppe wählst Du dir einen anderen Multiplikator aus, sagen wir 10.000. Also hast Du platz für 100 Untergruppen (sollten eh nicht soviele werden, oder es wird zu unübersichtlich und Du solltest generell mal über das problem der datendarstellung nachdenken). Alle Unter-Untergruppen (also Kinder der kinds-gruppen der Hauptgruppen) kriegen auch einen Basis-Multiplikator, sagen wir 100, das gibt dir 100 IDs Platz für diese Untergruppen und 100 Platz für Artikel (die eh nur bis 50 gehen oder eben getrennt angezeigt werden sollten).
Wie findest Du da nun schnell den passenden Knoten ? Ganz einfach.
Nehmen wir an, die gelesene ID ist 1.059.756
Im Rootknoten findest Du 5 Kinder, deine Hauptgruppen. Sie haben die IDs 1, 2, 3, 4 und 5.
Deine Formel ist (ID - 1) * BasisMultiplikator für die StartID, EndID ist StartID + BasisMultiplikator - 1
Das Erste brauchst Du nicht zu durchsuchen, die ID ist 1, also StartID ist 0, EndID ist 999.999. Und anstelle da jetzt reinzugehen und alle Kinderknoten zu durchsuchen, beschränken wir uns auf den Test, ob die von uns gesuchte ID in diesem Bereich liegt, das ist eh allemal schneller ;)
Im 2. Item ist die ID 2, also hast du den Bereich von 1.000.000-1.999.999. Nun liegt Deine ID in dem Bereich, also gehst Du rein und ziehst von der gesuchten ID den Basis-Multiplikator für Hauptgruppen ab. Übriug bleibt die ID 59.756 und Dein neuer Basis-Multiplikator ist 10.000. Also los un den Knoten suchen, der als basis die 6 hat ((ID - 1) * BasisMultiplikator), also zw. 50.000 und 60.000... dann wieder in die nächste Kinds-Ebene...
Das ist natürlich nur die Idee, und ist auch etwas abgefahrener als meine Idee für meine Resourcen, aber hey, smot kannst Du echt schnell Dein Zeug finden, selbst wenn du tausende Knoten hast (ok, nur, wenn sie nicht alle auf einer Ebene liegen, sondern dem von dir geschildertem Auftreten folgen.. aber es ist ja eine Lösung für dich ;) )
Wenn Du das gern umsetzen willst, dann können wir gemeinsam drüber nachdenken, falls Du problem hast... Oder vllt hat jemand eine solche Lösung parat (bin ja nicht der Einzigste, der abgefahrene Ideen hat !)
So, ich geh jetzt ins Bett, ist spät...
cyberax - Mo 19.02.07 22:44
@Grenzgaenger:
habe deinen Vorschalg probiert, aber hat bei mir das selbe Ergebnis geliefert wie meine Funktion. Sprich die Einträge waren nicht an der richtigen Stelle. Das liegt sicher daran, dass der Index in jedem Knoten immer wieder von 0 beginnt. Daher geht meine Funktion auch so nicht auf und es kommt zu falschen Einordnungen im Tree.
@helgelange:
Also finde die Idee von dir wirklich sehr gut. Doch (wie dir schon bekannt ist :) ) bin ich noch nicht ganz hinter das System gestiegen.
Also lieber erst einmal zu deinen Funktionen...
Frage(n):
Die variablen Resort und IL benötige ich ja (vorerst) nicht um das Tree zu füllen.
Quelltext
1:
| S := RC.GetString(pData^.ParentName); |
Ich müsste also an dieser Stelle mit einem zweiten Query den Rootnode suchen und den Name (???) des Nodes übergeben, auf den mittels "gr_id"(DB) von dem aktuellen Node (das erstellt werden soll) verwiesen wird ?? Ist dieser Name eindeutig??? "Autos" zum Beispiel kann doch mehrmals in dem Tree vorkommen....?!?
Oder wie erkenne ich sonst den Name des parents?
Und wofür steht das ...
Ich hoffe das auch ich das hier irgendwann mal verstehe und hoffe bis dahin auf eure weitere Mithilfe!
Danke schonmal für eure Mühen!!
Und gute Nacht...
HelgeLange - Mo 19.02.07 23:51
nah, RC.GetString ist für meinen Resourcen-manager, einfach raus
er liefert den Namen für den Knoten wieder (ist wichtig für Mehrsprachigkeit etc. bei mir).
pND^ zeigt auf meine pNodedata-struktur und HookNr ist eine Variable in der Strucktur, die ich an jedem node brauche. Kann für Dich auch raus.
IL ist die ImageList, brauch ich scheinbar nicht mehr in der Routine, weiss auch nicht, warum es noch da ist ;)
In "TreeFindItem" musst du vom Prinzip nur noch entscheiden, ob ein Eintrag noch weitere Untereinträge hat, oder ob du zum NextSibling gehst.
In TreeAddItem kannst Du alles rauswerfen, was mit Images zu tun hat oder HookNr. Du musst Dir eine Struktur für Deine Nodedata basteln (wenn Dir die ID nicht reichen sollte), ansonsten gibst Du an die Routine eben nur auch Deine Gr_ID mit und vergleichst sie jeweils mit der ID des augenblicklichen Nodes.
Delete - Mi 21.02.07 01:39
hier mal 'n kleines beispiel, schnell runtergeschrieben aber es sollte in etwa zeigen, wie das funktioniert...
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: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119:
| unit Unit1;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls, StdCtrls, contnrs;
type TForm1 = class(TForm) b: TButton; lb: TListBox; tv: TTreeView; od: TOpenDialog; procedure bClick(Sender: TObject); procedure tvDeletion(Sender: TObject; Node: TTreeNode); private public end;
var Form1: TForm1;
implementation
{$R *.dfm}
type tft= class (tobject) grp, nd: integer; bez: string; end;
procedure TForm1.bClick(Sender: TObject); procedure LoadAndSplit(const sl: tstringlist; const ol: tobjectList); var i: integer; ft: tft; s : string; begin for i := 0 to sl.Count - 1 do begin ft:= tft.create; s := sl.Strings[i]; if s <> '' then begin ft.grp:= strtoint(copy(s,1,pos(',',s)-1)); delete(s,1,pos(',',s)); ft.nd:=strtoint(copy(s,1,pos(',',s)-1)); delete(s,1,pos(',',s)); ft.bez := s; ol.add(ft); end; end; end; procedure AddTV(const ol: tObjectList; nd: ttreenode); function AddNode(const ol: tObjectList;nd: tTreeNode): ttreenode; var dta: tft; begin dta := tft.Create; dta.grp := tft(ol.Items[0]).grp; dta.nd := tft(ol.items[0]).nd; dta.bez := tft(ol.items[0]).bez; result := tv.Items.AddChildObject(nd, tft(ol.Items[0]).bez, dta) end; begin if ol.Count > 0 then begin if tft(ol.Items[0]).grp = 0 then nd := NIL; if nd = NIL then nd:=addnode(ol, nd) else if tft(nd.Data).nd = tft(ol.Items[0]).grp then nd := addnode(ol, nd) else nd := addnode(ol, nd.GetNext); ol.Delete(0); AddTV(ol,nd); end; end; var sl: tstringlist; ol: tobjectlist; i : integer; begin od.Filter := 'textdatei | *.txt'; if od.execute and fileexists(od.FileName) then begin sl := tstringlist.Create; try sl.loadfromfile(od.FileName); ol := tobjectlist.Create; try LoadAndSplit(sl, ol); for i := 0 to ol.Count - 1 do lb.Items.Add(inttostr(tft(ol.Items[i]).grp)+', '+inttostr(tft(ol.items[i]).nd)+', '+tft(ol.items[i]).bez); AddTV(ol, NIL); finally ol.Free; end; finally sl.free; end; end; end;
procedure TForm1.tvDeletion(Sender: TObject; Node: TTreeNode); begin if (node <> NIL) and (node.data <> NIL) then begin tobject(node.data).free; node.Data := NIL; end; end;
end. |
es wird vorausgesetzt, dass die eingabedatei sortiert ist.
alles andere sollte bereits im thread erwähnt sein. wenn du grossere sprünge hast, musst evtl. bei der IF anweisung in der form AddTV anpassungen vornehmen.
<HTH>
uwewo - Mi 21.02.07 08:58
Hallo,
warum musst Du denn überhaupt alle Daten sofort in das TreeView schreiben?
Ich hatte das mal wie folgt gelöst:
Erstelle zuerst die Haupgruppen mit einem Dummy Eintrag damit das + vor den Hauptgruppen erscheint, jetzt reagierst Du erst beim öffnen einer Hauptgruppe
Delphi-Quelltext
1: 2:
| procedure TForm.TreeListExpanding(Sender: TObject; Node: TTreeNode; var AllowExpansion: Boolean); |
und führst hier die neue SQL Anweisung aus, um die Hauptgruppe mit Untergruppen zu füllen usw. Das spart Zeit und Speicher beim öffnen des Formulars, ausserdem ist es auch übersichtlicher zu programmieren.
Nur eine Idee
cyberax - Mi 21.02.07 19:45
Kurze Zwischenfrage: wie kann ich denn meine Integer Variable (gr_id) einem Treenode zuweisen?
Quelltext
1:
| MyTreeNode := artikel_baum.tv_eg1.Items[mygr_id]; |
So funktioniert es jedenfalls nicht, da so das treenode immer nil bzw. "()"...?!? was bewirkt, dass immer nur ROOT Einträge erstellt werden
Delete - Mi 21.02.07 22:36
ganz einfach, wenn du in den source lurkst, den ich dir gab.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| function AddNode(const ol: tObjectList;nd: tTreeNode): ttreenode; var dta: tft; begin dta := tft.Create; dta.grp := tft(ol.Items[0]).grp; dta.nd := tft(ol.items[0]).nd; dta.bez := tft(ol.items[0]).bez; result := tv.Items.AddChildObject(nd, tft(ol.Items[0]).bez, dta) end; |
du kannst nicht direkt mit 'ner id 'n neuen node erzeugen, hierfür musst du ein neues objekt erzeugen (läuft normal automatisch, in AddChild). lurk mal dort unter der hilfe, es gibt da 'ne menge an methoden die dafür gedacht sind, 'n node an den tree zu heften.
<HTH>
cyberax - So 25.02.07 22:33
Hallo,
ich glaube ich hab's nun endlich (mit eurer Hilfe) hinbekommen. :D
Hier mal der Code:
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:
| function TForm1.GetFieldList: TStringList; var gruppen_id:integer; akt_id:integer; akt_gruppe:integer; id_wert:integer; begin FieldList.clear; with form1.ZQuery2 do begin sql.Text:='SELECT * FROM GRUPPE WHERE ID = :GRUPPE'; With Params.ParamByName('GRUPPE') do begin DataType := ftinteger; Value := form1.ZQuery1.fieldbyname('GR_ID').asinteger; End; open; end; if ZQuery2.RecordCount>0 then if ZQuery2.FieldByName('gr_id').asinteger > 0 then begin akt_id:=ZQuery2.fieldbyname('id').AsInteger; akt_gruppe:=zQuery2.fieldbyname('gr_id').AsInteger; repeat with form1.ZQuery2 do begin sql.Text:='SELECT * FROM GRUPPE WHERE ID = :GRUPPE'; With Params.ParamByName('GRUPPE') do begin DataType := ftinteger; Value := akt_gruppe; End; open; end; akt_gruppe:=ZQuery2.fieldbyname('GR_ID').AsInteger; if akt_gruppe=0 then repeat id_wert:= ZQuery2.fieldbyname('ID').asinteger; FieldList.add(ZQuery2.fieldbyname('NAME').asstring); with form1.ZQuery2 do begin sql.Text:='SELECT * FROM GRUPPE WHERE gr_id = :GRUPPE'; With Params.ParamByName('GRUPPE') do begin DataType := ftinteger; Value := id_wert; End; open; end; until ID_wert = AKT_ID; until akt_gruppe=0; end else repeat FieldList.add(ZQuery2.fieldbyname('NAME').asstring); until ZQuery2.FindNext=false; FieldList.add(ZQuery1.fieldbyname('NAME').asstring);
Result := FieldList; end; |
Das Problem ist halt, dass ich erst in die eine Richtung nach weiteren Einträgen / Knoten suchen und dann in die andere. Sicher kann man das Verbessern. Nur leider hat's bei mir nie funktioniert, so wie ich's probiert hatte.
Bin für Verbesserungsvorschläge und Fragen offen.
Danke schonmal auch für die bisherige Hilfe!!!
Gruß cyberax
cyberax - Mo 26.02.07 20:49
Hallo nochmal,
hier nochmal eine geänderte Variante der Function. Konnte bis jetzt keine "Fuktionsstörungen" festestellen....
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:
| function TForm1.GetFieldList: TStringList; var akt_gruppe:integer; i,j:integer; begin FieldList.clear; with form1.ZQuery2 do begin sql.Text:='SELECT * FROM GRUPPE WHERE ID = :GRUPPE'; With Params.ParamByName('GRUPPE') do begin DataType := ftinteger; Value := form1.ZQuery1.fieldbyname('GR_ID').asinteger; End; open; end; if ZQuery2.RecordCount>0 then if ZQuery2.FieldByName('gr_id').asinteger > 0 then begin repeat FieldList.add(ZQuery2.fieldbyname('NAME').asstring); akt_gruppe:=zQuery2.fieldbyname('gr_id').AsInteger; with form1.ZQuery2 do begin sql.Text:='SELECT * FROM GRUPPE WHERE ID = :GRUPPE'; With Params.ParamByName('GRUPPE') do begin DataType := ftinteger; Value := akt_gruppe; End; open; end; until akt_gruppe=0; i:= 0; j := FieldList.count-1; while i<j do begin FieldList.Exchange(I,J); inc(i); dec(j) end; end else repeat FieldList.add(ZQuery2.fieldbyname('NAME').asstring); until ZQuery2.FindNext=false; FieldList.add(ZQuery1.fieldbyname('NAME').asstring); Result := FieldList; end; |
Gruß cyberax
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2026 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!