Entwickler-Ecke

Datenbanken - Treeview aus Tabelle geladen


cyberax - Do 15.11.07 17:22
Titel: Treeview aus Tabelle geladen
Hallo,

ich hatte hier...

http://www.delphi-forum.de/topic_Treeview+Struktur+aus+Tabelle+laden_77318.html

schon das "Problem" mit dem Treeview angesprochen. Die Darstellung war soweit gelöst. Nur bekomm ich eines einfach nicht hin.

Wenn ich zum Beispiel einen Ast des Trees ändern möchte, muss ich immer erst bis zum letzten Zweig vordringen damit der richtige Datensatz angezeigt wird und kann mich dann zurück bis zu dem Ast arbeiten, denn ich eigentlich bearbeiten möchte.

Hier ein Bsp. dazu:


Quelltext
1:
2:
3:
4:
5:
6:
- Fahrräder
- Autos
  - schnelle
  - langsame
    - Trabi
      - 601'er


Wenn ich nun "langsame" ändern (oder erstmal selektieren) möchte, dann muss ich immer erst bis zum letzten Zweig -> in dem Fall "601'er" vordringen. WEnn ich auf diesen Zweig klicke, stimmt die "Verlinkung" bzw. der Bezug zur Tabelle aus der ich den Tree lade. Nun kann ich auf "langsame" drücken und der Bezug passt und ich kann diesen Datensatz bearbeiten. Dieser Fehler ist auch immer nur beim ersten mal in jedem Zweig.

Habt ihr eine Idee, woran das liegt?

Mfg Cyberax


Logikmensch - Fr 16.11.07 07:42

Das bedeutet doch, wenn ich das richtig verstehe, dass Du den Baum in der Datenbank direkt speicherst und zwar, wenn ich das richtig interpretiert habe, bildest Du die 5 verschiedenen Levels direkt als Spalten der Tabelle ab.
Frage: Warum speicherst Du den Level nicht als Zahl (node.Level) in nur einer Spalte ab und den Text des Knoten (node.Caption bzw. node.Text) in einer anderen? Wäre das nicht eleganter?


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
- Fahrräder  
- Autos  
  - schnelle  
  - langsame  
    - Trabi  
      - 601'er 

ID: Level:  Caption:
=== ======  ========
1   0       Fahrräder
2   0       Autos
3   1       schnelle
4   1       langsame
5   2       Trabi
6   3       601'er

Oder aber: Den Pfad des Knotens als einfachen Textstring in einer Text-Spalte führen, z.B. für Dein Beispiel:


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
- Fahrräder  
- Autos  
  - schnelle  
  - langsame  
    - Trabi  
      - 601'er 

Würde dann in der Tabelle liegen als:

1. Fahrräder
2. Autos
3. Autos\schnelle
4. Autos\langsame
5. Autos\langsame\Trabi
6. Autos\langsame\Trabi\601'er


Das Suchen eines Eintrages ist in der zweiten Variante ganz besonders einfach, denn Du müsstest zum Suchen nur den Pfad des Knotens (rekursiv) ermitteln.

Ist mir nur so durch den Kopf gegangen.


cyberax - Sa 17.11.07 17:36

Hallo Logikmensch,

Zitat:
Frage: Warum speicherst Du den Level nicht als Zahl (node.Level) in nur einer Spalte ab und den Text des Knoten (node.Caption bzw. node.Text) in einer anderen? Wäre das nicht eleganter?

... dass mach ich doch genau so. Meine Tabelle sieht so aus, wie du es auch vorgeschlagen hast. In dem angführten Link hatte ich geschrieben, dass meine Abrage (SQL) die ich zum Auslesen dieser Tabelle hatte leider etwas zu langsam war. Daher habe ich sie dann (mit der Hilfer der anderen aus dem Forum) umgestellt (wie?->siehe Link). Doch seitdem habe ich das hier beschriebene Problem.....


alzaimar - Sa 17.11.07 18:14

So speichert man aber keinen Baumin in einer DB, sondern so:

Wenn Du nun einen Index auf die Parent-Spalte (ID sowieso) legst, dann kannst Du für jeden Knoten sofort seine Kinder herausfinden.
Das Bewegen eines ganzen Teilbaumes geschieht durch Änderung eines einzigen Wertes, nämlich der ParentID der Teilbaumwurzel.

So ziemliche jedes datensensitive TTreeview-Derivat verlangt zwei Feldnamen, um so einen Baum anzuzeigen, nämlich die 'ID' sowie die 'ParentID' und zusätzlich noch der Wert für die Wurzel (hier: NULL).


Logikmensch - Mo 19.11.07 07:06

Hallo alzaimar,
Damit hast Du recht. Das ist dann GANZ professionell. Guter Tipp, hätt ich auch drauf kommen können (müssen).


cyberax - Mi 21.11.07 19:35

Hallo,

Zitat:

ID Parent Data
1 NULL Fahrräder
2 NULL Autos
3 2 schnelle
4 2 langsame
5 4 Trabi
6 5 601'er


...

Ich habe es nach diesem Schema bei mir aufgebaut. Hatte das von Logikmensch am Anfang nicht richtig gelesen. Sorry. Aber wie geschrieben, dass problem welches ich oben beschrieben habe besteht immer noch. Ein Idee??

Gruß cyberax


alzaimar - Mi 21.11.07 20:29

Ich verstehe dein Problem einfach nicht. Du speicherst in der Data-Eigenschaft der TTreeNode die ID des Datensatzs. Wenn Du den anklickst, hast du doch die TTreenode (Selected oder so) und damit auch die ID ( Integer (SelectedNode.Data) ).


cyberax - Do 22.11.07 18:34

Okay, dann hier nochmal meine akt. Codes dafür.

Zum Aufbau des Treeviews:


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:
 with ZQuery1 do begin
  sql.Text:='SELECT t1.name, t1.id, t1.gr_id, t1.bild, t1.artikel_id, t1.kategorie, '+
             't2.name as n2, t2.id as id2, t2.gr_id as gr_id2, t2.bild as bild2, t2.artikel_id as artid2, t2.kategorie as kat2, '+
             't3.name as n3, t3.id as id3, t3.gr_id as gr_id3, t3.bild as bild3, t3.artikel_id as artid3, t3.kategorie as kat3, '+
             't4.name as n4, t4.id as id4, t4.gr_id as gr_id4, t4.bild as bild4, t4.artikel_id as artid4, t4.kategorie as kat4, '+
             't5.name as n5, t5.id as id5, t5.gr_id as gr_id5, t5.bild as bild5, t5.artikel_id as artid5, t5.kategorie as kat5, '+
             't6.name as n6, t6.id as id6, t6.gr_id as gr_id6, t6.bild as bild6, t6.artikel_id as artid6, t6.kategorie as kat6, '+
             't7.name as n7, t7.id as id7, t7.gr_id as gr_id7, t7.bild as bild7, t7.artikel_id as artid7, t7.kategorie as kat7, '+
             't8.name as n8, t8.id as id8, t8.gr_id as gr_id8, t8.bild as bild8, t8.artikel_id as artid8, t8.kategorie as kat8, '+
             't1.name AS lev1, t2.name AS lev2, t3.name AS lev3, t4.name AS lev4,t5.name AS lev5, t6.name AS lev6 , t7.name AS lev7 , t8.name AS lev8 '+
             'FROM gruppe AS t1 '+
             'LEFT JOIN gruppe AS t2 ON t2.gr_id = t1.id '+
             'LEFT JOIN gruppe AS t3 ON t3.gr_id = t2.id '+
             'LEFT JOIN gruppe AS t4 ON t4.gr_id = t3.id '+
             'LEFT JOIN gruppe AS t5 ON t5.gr_id = t4.id '+
             'LEFT JOIN gruppe AS t6 ON t6.gr_id = t5.id '+
             'LEFT JOIN gruppe AS t7 ON t7.gr_id = t6.id '+
             'LEFT JOIN gruppe AS t8 ON t8.gr_id = t7.id '+
             'WHERE t1.gr_id = :wert ORDER by name';
 parambyname('wert').asinteger:=0;
 open;
 end;
 x:=ZQuery1.recordcount;
 FieldList := TStringList.create;
 TreeView1.items.BeginUpdate;
 ZQuery1.first;
 while not ZQuery1.eof do
 begin
  fieldlist.Clear;
  fieldlist.add(ZQuery1.fieldbyname('lev1').asstring);
  if ZQuery1.fieldbyname('lev2').asstring<>''then fieldlist.add(ZQuery1.fieldbyname('lev2').asstring);
  if ZQuery1.fieldbyname('lev3').asstring<>''then fieldlist.add(ZQuery1.fieldbyname('lev3').asstring);
  if ZQuery1.fieldbyname('lev4').asstring<>''then fieldlist.add(ZQuery1.fieldbyname('lev4').asstring);
  if ZQuery1.fieldbyname('lev5').asstring<>''then fieldlist.add(ZQuery1.fieldbyname('lev5').asstring);
  if ZQuery1.fieldbyname('lev6').asstring<>''then fieldlist.add(ZQuery1.fieldbyname('lev6').asstring);
  if ZQuery1.fieldbyname('lev7').asstring<>''then fieldlist.add(ZQuery1.fieldbyname('lev7').asstring);
  if ZQuery1.fieldbyname('lev8').asstring<>''then fieldlist.add(ZQuery1.fieldbyname('lev8').asstring);
  TreeAddItem(TreeView1, fieldlist, ZQuery1.getBookmark, false);
  ZQuery1.next;
 end;
 FieldList.clear;
 TreeView1.Alphasort;
 TreeView1.items.Endupdate;


Hatte ich vorher anders, aber das hat beim Laden/Aufbau ewig gedauert (DB liegt online).

Wenn ich den Treeview selektiere:


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:
 if DataSource1.enabled then ZQuery1.gotobookmark(node.Data);
 if node.text = ZQuery1.fieldbyname('lev1').asstring then begin
  dbedit1.dataField:='gr_id';
  dbedit3.dataField:='name';
  dbtext1.dataField:='id';
  dbedit2.dataField:='bild';
  dbedit4.dataField:='artikel_id';
  DBCheckBox1.dataField:='kategorie';
 end;
 if node.text = ZQuery1.fieldbyname('lev2').asstring then begin
  dbedit1.dataField:='gr_id2';
  dbedit2.dataField:='bild2';
  dbedit3.dataField:='n2';
  dbtext1.dataField:='id2';
  dbedit4.dataField:='artid2';
  DBCheckBox1.dataField:='kat2';
 end;
 if node.text = ZQuery1.fieldbyname('lev3').asstring then begin
  dbedit1.dataField:='gr_id3';
  dbedit2.dataField:='bild3';
  dbedit3.dataField:='n3';
  dbtext1.dataField:='id3';
  dbedit4.dataField:='artid3';
  DBCheckBox1.dataField:='kat3';
 end;
 if node.text = ZQuery1.fieldbyname('lev4').asstring then begin
  dbedit1.dataField:='gr_id4';
  dbedit2.dataField:='bild4';
  dbedit3.dataField:='n4';
  dbtext1.dataField:='id4';
  dbedit4.dataField:='artid4';
  DBCheckBox1.dataField:='kat4';
 end;
 if node.text = ZQuery1.fieldbyname('lev5').asstring then begin
  dbedit1.dataField:='gr_id5';
  dbedit2.dataField:='bild5';
  dbedit3.dataField:='n5';
  dbtext1.dataField:='id5';
  dbedit4.dataField:='artid5';
  DBCheckBox1.dataField:='kat5';
 end;
 if node.text = ZQuery1.fieldbyname('lev6').asstring then begin
  dbedit1.dataField:='gr_id6';
  dbedit2.dataField:='bild6';
  dbedit3.dataField:='n6';
  dbtext1.dataField:='id6';
  dbedit4.dataField:='artid6';
  DBCheckBox1.dataField:='kat6';
 end;
 if node.text = ZQuery1.fieldbyname('lev7').asstring then begin
  dbedit1.dataField:='gr_id7';
  dbedit2.dataField:='bild7';
  dbedit3.dataField:='n7';
  dbtext1.dataField:='id7';
  dbedit4.dataField:='artid7';
  DBCheckBox1.dataField:='kat7';
 end;
 if node.text = ZQuery1.fieldbyname('lev8').asstring then begin
  dbedit1.dataField:='gr_id8';
  dbedit2.dataField:='bild8';
  dbedit3.dataField:='n8';
  dbtext1.dataField:='id8';
  dbedit4.dataField:='artid8';
  DBCheckBox1.dataField:='kat8';
 end;


Diese Variante "musste" ich aus besagten Ladezeitgründen nehmen. Vorher hatte ich diese... [http://www.delphi-forum.de/...lle+laden_77318.html]!
ich greife als nicht direkt auf die Data Eigenschaft zu.
Gruß cyberax


alzaimar - Do 22.11.07 19:37

So macht man das ja auch nicht.

Wieo limitierst du das auf 8 Ebenen und machst ein grausamen Self Left Join?

Schnapp Dir einfach jeden Record und füge denm an die richtige Stelle im Baum ein. Der Vaterknoten ist ja der, dessen Id der ParentID des einzufügenden Knotens entspricht.

Wenn Du die Baumdaten nach ParentID sortierst, dann sparst du dir meistens das suchen, weil ja die ParentID des einzufügenden Knotens meistens die ist, die eben gesucht wurde. Wenn du verstehst, was ich meine.

Die ID packst du wirklich in die Data-Eigenschaft des TreeNodes und kannst dann, wie ich beschrieben habe, die Daten anzeigen.

Alles andere ist Quark.


BenBE - Do 22.11.07 19:42

Ich hatte für sowas vor nicht allzu langer Zeit mal nen kleinen Tipp gegeben, wie man relativ einfach den Baum ohne großen Baum rekursiv aufbauen kann. Einfach mal suchen.


cyberax - Do 22.11.07 22:23

Sorry, aber ich finde nichts dazu, was mir weiter hilft. Könntet ihr mir evtl. ein Beispiel Code geben?

Danke!


BenBE - Do 22.11.07 22:29

http://www.delphi-forum.de/viewtopic.php?t=77318


cyberax - Mo 26.11.07 20:38

Ja so, wie du es da vorgeschlagen hast, hatte ich es ja auch gemacht (wie ich dort auch geschrieben habe). Nur war das zu langsam. Oder hatte ich da was falsch verstanden?


BenBE - Mo 26.11.07 22:04

Dann schau mal gezielt nach, wo Zeit verbraten wird ...


alzaimar - Mo 26.11.07 23:06

Grundsätzlich ist eine Treeview unglaublich langsam. Also, was tun?

Die billige und schnelle Lösung: Nimm Dir ein TVirtualTreeView und füll das. Das wird vermutlich schneller sein, aber auch irgendwann an seine Grenzen stoßen.

Die elegante Lösung (schließt die erste Möglichkeit aber nicht aus): Fülle nur die 1.Ebene. Jeder Knoten bekommt ein 'Dummy-Kind-Knoten'. Wenn der Anwender nun auf das '+' klickt, schaust Du im OnExpanding-Event nach, ob das Kind ein 'Dummy-Kind' ist. Wenn ja, löschst Du das Dummy-Kind und rufst nur die Knoten aus der DB ab, deren Vater-ID der ID des zu expandierenden Knotens entspricht. Jeder dieser Kind-Knoten bekommt wieder einen 'Dummy-Kind-Knoten'. usw.

Wozu soll man denn auch 300 oder mehr Knoten auf einmal in einen Baum eintragen? Das kann doch eh kein Mensch überblicken...


cyberax - Di 27.11.07 11:36

Ach ich kann Treeview's einfach nicht leiden!

Okay, dann werde ich mich mal auf die Suche nach Zeiträubern machen und die Möglichkeit von alzaimar probieren.

Danke euch erstmal!