Habe das Projekt angefügt, wenn jemand Lust und Zeit hast, könnte sich das anschauen.
Welche uses braucht GetSubDirectories?
Re: Welche uses braucht GetSubDirectories?
Ok... hatte ich auch so gedacht... Die TreeView bleibt aber leer... 
Habe das Projekt angefügt, wenn jemand Lust und Zeit hast, könnte sich das anschauen.
Habe das Projekt angefügt, wenn jemand Lust und Zeit hast, könnte sich das anschauen.
- Dateianhänge
-
treeviewnachbuch.zip- (276.03 KiB) 103-mal heruntergeladen
Re: Welche uses braucht GetSubDirectories?
Ja, da stimmt ja auch nicht viel.Lion hat geschrieben:Ok... hatte ich auch so gedacht... Die TreeView bleibt aber leer...![]()
Was willst du eigentlich erreichen?
Möchtest du dich nicht lieber an TShellTreeView orientieren?
Das funktioniert immerhin schon mal.
Re: Welche uses braucht GetSubDirectories?
Der Fehler bedeutet, daß der Node, den Du übergibst (z.B. TreeView1.Items[0]) noch gar nicht existiert. Wenn Du mit dem TreeView arbeitest, mußt Du den zuerst erstellen, wie's sehr viel einfacher geht, dazu anschließend.Lion hat geschrieben:Ja, aber es knallt, siehe Screenshot. Weiß jetzt nicht was der Fehler bedeutet.
Nodes sind einfach Einträge im TreeView. Die heißen Nodes ("Knoten"), weil sie sowohl einen Elternteil und Kinder, als auch Geschwister haben können, bzw. graphisch gesehen: Verbindungen nach oben, unten, links und rechts haben. Deshalb haben Nodes auch eine etwas komplizierte Datenstruktur, sie müssen sich z.B. merken, wie ihre Eltern, Geschwister und Kinder heißen - wie im richtigen Leben halt...
Leicht modifiziert, funktioniert Dein Listing dann doch:
Code: Alles auswählen
Function TForm1.ShowDirectory(Const ADir: String; TV: TTreeView;
ParentNode: TTreeNode): Integer;
Var I,II: Integer;
N: TTreeNode;
L: TStringList;
D: String;
begin
{ Statt dem hier:
L := TStringList.Create;
try
Result := GetSubDirectories(ADir, L);
nimm das: }
L := FindAllDirectories(ADir, false);
try
Result := L.Count;
{ Und dann weiter wie gehabt: }
if result > 0 then ...
Der Aufruf geht wie gesagt deshalb schief, weil Du einen nicht existenten Node übergibst. Du brauchst dem TreeView gegenüber nur ehrlich zu sein und ihm stattdessen "nil" übergeben, schon funktioniert es:
Code: Alles auswählen
ShowDirectory('C:', TreeView1, nil);Ich bin mir aber nicht sicher, ob das wirklich das ist, was Du haben möchtest. Einen Verzeichnisbaum von Hand zu programmieren, mag lehrreich sein, aber dafür gibt es schon eine fertige Lösung: TShellTreeView (ist im Komponentenreiter "Misc"). Da gibst Du Dein gewünschtes Verzeichnis in die die Property "Root" ein (im Objektinspektor oder zur Laufzeit), und fertig ist die Laube!
Gruß Rüdiger
Re: Welche uses braucht GetSubDirectories?
Danke Dir ruewa! Das funktioniert, aber das Problem ist, es wird nur die erste Ebene der Verzeichnisstruktur angezeigt, also es werden keine Unterverzeichnisse eingelesen. Kann es sein, weil ParentNode nil ist?
Wieso ich kein ShellTreeView nehme habe ich hier kurz geschildert. http://www.lazarusforum.de/viewtopic.php?f=18&t=8499
Wieso ich kein ShellTreeView nehme habe ich hier kurz geschildert. http://www.lazarusforum.de/viewtopic.php?f=18&t=8499
Re: Welche uses braucht GetSubDirectories?
Um das zu ändern, könntest Du den 2. Parameter von FindAllDirectories auf "true" setzen. Aber das kann dann frustrierend lange dauern, weil die Funktion Deinen Rechner dann u.U. bis in die hinterste Ecke durchstöbert.Lion hat geschrieben:aber das Problem ist, es wird nur die erste Ebene der Verzeichnisstruktur angezeigt, also es werden keine Unterverzeichnisse eingelesen.
Aber auch das ist Krampf, dann bekommst Du zwar eine endlose Auflistung, aber auch wieder keinen echten Verzeichnisbaum. Im Grunde müßtest Du von Hand auf jeder Ebene jedes Verzeichnis wiederum durch Dein "ShowDirectory" jagen, dann eben mit dem jeweiligen Verzeichnis als Parent. Am Ende hättest Du bestenfalls einen Humunculus von ShellTreeView erschaffen...
Das war mir entgangen, entschuldige. Da hatte Theo aber völlig recht: Die ShellTreeView lädt nicht den kompletten Verzeichnisbaum auf einmal ein, sondern schaut bei Bedarf nach der angeforderten Ebene. Auf diese Weise vermeidet sie das oben genannte Zeitproblem.Lion hat geschrieben:Wieso ich kein ShellTreeView nehme...
Soweit ich sehe, stört Dich ja, daß im eingeklappten Zustand von Anfang an unklar ist, ob ein angezeigtes Verzeichnis Unterverzeichnisse enthält oder nicht. Gefühlsmäßig würde ich da aber eher bei einer ShellTreeView ansetzen, da wird es sicher Möglichkeiten geben (vielleicht programmatisch auf- und gleich wieder einklappen oder irgend sowas).
Gruß Rüdiger
Zuletzt geändert von ruewa am Di 3. Feb 2015, 18:40, insgesamt 1-mal geändert.
Re: Welche uses braucht GetSubDirectories?
Ja mich stören die [+]-Zeichen bei leeren Verzeichnissen. Könnte ich das wegbekommen, würde mir das schon reichen. 
Re: Welche uses braucht GetSubDirectories?
Hallo Lion,
stimmt, das würde mich auch stören. Ich hab mir das nun etwas genauer angesehen und eine Möglichkeit gefunden, das abzustellen. Muß aber dazu sagen, daß ich mit der ShellTreeView bisher nicht viel gemacht habe und ihr Innenleben daher nicht gut kenne. Es dürfte also sicherlich noch bessere Wege geben.
Es ist so, wie Theo sagte: Die ShellTreeView lädt nur das, was sie augenblicklich braucht, ihr Items.Count wächst beständig an, je mehr Ordner man öffnet. Es sieht so aus, als würde die Property TTreenode.HasChildren anfänglich auf true initialisiert, ohne echte Prüfung. Das ist wahrscheinlich der Ursprung des Problems.
Überhaupt finde ich die Komponente - freundlich ausgedrückt - doch arg sperrig, das Ändern mancher Eigenschaften (z.B. von AutoExpand oder HotTrack) hat bei mir überhaupt nichts bewirkt. Und daß ein Doppelklick mal Expandieren bewirkt und mal das Editieren des Dateinamens (so ist's jedenfalls bei mir), ist schon ziemlich nervig (das kann man bestimmt auch irgendwo abstellen, hab's nur nicht gefunden auf die Schnelle). Die Dokumentation ist auch beklagenswert. Aber okay, mit etwas Gebastel erfüllt das Ding dann schon seinen Zweck. Was Du bemängelst, die Anfangsbehauptung von Unterverzeichnissen, wo keine sind, läßt sich relativ einfach beheben - es ist nur mühsam, den Ansatzpunkt zu finden. Folgende Prozedur gehört eigentlich in die ShellTreeView-Komponente selbst, aber der Einfachheit halber habe ich sie als Methode von TForm1 implementiert:
Das ist es dann schon weitgehend. Die letzte Schwierigkeit besteht nur noch darin, das passende Event zu finden, wo man den Aufruf plazieren kann, ohne daß er ständig durchlaufen wird und Zeit raubt, aber doch da ist, wenn man ihn braucht. Das einzig Passende, das ich gefunden habe, ist TShellTreeView.OnCustomDraw:
Wie gesagt: Ist nur ein Workaround und sicher nicht das Gelbe vom Ei. Aber so funktioniert es jetzt glaube ich ganz gut, auch mit einem akzeptablen Zeitverhalten. Sonst müßte man halt in die Komponente selbst gehen und da wäre vermutlich der aussichtsreichste Weg der, dafür zu sorgen, daß TTreeNode.HasChildren beim Initialisieren/Hinzufügen gleich überprüft und korrekt gesetzt wird.
Gruß Rüdiger
stimmt, das würde mich auch stören. Ich hab mir das nun etwas genauer angesehen und eine Möglichkeit gefunden, das abzustellen. Muß aber dazu sagen, daß ich mit der ShellTreeView bisher nicht viel gemacht habe und ihr Innenleben daher nicht gut kenne. Es dürfte also sicherlich noch bessere Wege geben.
Es ist so, wie Theo sagte: Die ShellTreeView lädt nur das, was sie augenblicklich braucht, ihr Items.Count wächst beständig an, je mehr Ordner man öffnet. Es sieht so aus, als würde die Property TTreenode.HasChildren anfänglich auf true initialisiert, ohne echte Prüfung. Das ist wahrscheinlich der Ursprung des Problems.
Überhaupt finde ich die Komponente - freundlich ausgedrückt - doch arg sperrig, das Ändern mancher Eigenschaften (z.B. von AutoExpand oder HotTrack) hat bei mir überhaupt nichts bewirkt. Und daß ein Doppelklick mal Expandieren bewirkt und mal das Editieren des Dateinamens (so ist's jedenfalls bei mir), ist schon ziemlich nervig (das kann man bestimmt auch irgendwo abstellen, hab's nur nicht gefunden auf die Schnelle). Die Dokumentation ist auch beklagenswert. Aber okay, mit etwas Gebastel erfüllt das Ding dann schon seinen Zweck. Was Du bemängelst, die Anfangsbehauptung von Unterverzeichnissen, wo keine sind, läßt sich relativ einfach beheben - es ist nur mühsam, den Ansatzpunkt zu finden. Folgende Prozedur gehört eigentlich in die ShellTreeView-Komponente selbst, aber der Einfachheit halber habe ich sie als Methode von TForm1 implementiert:
Code: Alles auswählen
procedure TForm1.UpdateShellTreeView(STV: TShellTreeView);
var
TN : TTreeNode;
begin
STV.BeginUpdate;
TN := STV.Items.GetFirstVisibleNode;
Repeat
if not TN.Expanded then
begin
TN.Expand(false);
TN.Collapse(false);
end;
TN := TN.GetNextVisible;
until TN = nil;
STV.EndUpdate;
end;Code: Alles auswählen
procedure TForm1.ShellTreeView1CustomDraw(Sender : TCustomTreeView;
const ARect : TRect; var DefaultDraw : Boolean);
begin
UpdateShellTreeView(ShellTreeView1);
end;Gruß Rüdiger
Re: Welche uses braucht GetSubDirectories?
Danke Dir!
Habe es eben ausprobiert und die TreeView verhält sich irgendwie komisch. Bei sehr großen Verzeichnissen wird sie einfach leer und beim Schließen reagiert die Anwendung nicht, ist wohl im Hintergrund am arbeiten.
Ich selber habe mir folgendes gebastelt, da habe ich zwar noch immer alle [+]-Zeichen, aber dafür bekommen alle leeren Verzeichnisse ein "leeres"-Icon. Man kann dann zumindest an den Icons erkennen, dass das Verzeichnis leer ist. Ist aber nur beim Start der Anwendung so, muss schauen, dass das auch beim arbeiten immer so bleibt.
Habe es eben ausprobiert und die TreeView verhält sich irgendwie komisch. Bei sehr großen Verzeichnissen wird sie einfach leer und beim Schließen reagiert die Anwendung nicht, ist wohl im Hintergrund am arbeiten.
Ich selber habe mir folgendes gebastelt, da habe ich zwar noch immer alle [+]-Zeichen, aber dafür bekommen alle leeren Verzeichnisse ein "leeres"-Icon. Man kann dann zumindest an den Icons erkennen, dass das Verzeichnis leer ist. Ist aber nur beim Start der Anwendung so, muss schauen, dass das auch beim arbeiten immer so bleibt.
Code: Alles auswählen
procedure TMainForm.LoadRootFolderToShellTreeView();
var
i: Integer;
sl: TStringList;
begin
ShellTreeView1.BeginUpdate;
ShellTreeView1.Root := gvDirectory;
for i := 0 to ShellTreeView1.Items.Count - 1 do
begin
sl := FindAllDirectories(ShellTreeView1.GetPathFromNode(ShellTreeView1.Items[i]), false);
if sl.Count <= 0 then
begin
// hier wollte ich die [+]-Zeichen ausschalten, aber geht nicht, es wird entweder für das ganze TreeView ein- oder ausgeschaltet
//ShellTreeView1.ShowButtons := False;
ShellTreeView1.Items[i].ImageIndex := 1;
end else
begin
//ShellTreeView1.ShowButtons := True;
ShellTreeView1.Items[i].ImageIndex := 0;
end;
end;
ShellTreeView1.EndUpdate;
end;
- Dateianhänge
-
- Screenshot.JPG (24.52 KiB) 2987 mal betrachtet
Re: Welche uses braucht GetSubDirectories?
Oder einfach so?
Bei OnCustomDrawItem einhängen.
Bei OnCustomDrawItem einhängen.
Code: Alles auswählen
procedure TForm1.ShellTreeView1CustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
var sl: TStringList;
begin
sl := FindAllDirectories(TShellTreeView(Sender).GetPathFromNode(Node), false);
Node.HasChildren:= sl.Count > 0;
sl.free;
end; Re: Welche uses braucht GetSubDirectories?
Hm... verstehe ich nicht ganz... und was ist mir der LoadRootFolderToShellTreeView();?
Re: Welche uses braucht GetSubDirectories?
Wieso? Funktioniert das nicht bei dir?Lion hat geschrieben:Hm... verstehe ich nicht ganz... und was ist mir der LoadRootFolderToShellTreeView();?
LoadRootFolderToShellTreeView() ist von dir, das musst du selber wissen, was damit ist.
P.S. man könnte es natürlich noch effizienter machen, denn man braucht ja nicht wissen, wieviele Unterverzeichnisse da sind, sondern nur, ob es eines gibt.
Zuletzt geändert von theo am Mi 4. Feb 2015, 12:30, insgesamt 1-mal geändert.
Re: Welche uses braucht GetSubDirectories?
Also wenn ich dein Vorschlag dazu nehme, dann sieht es schon mal gut aus.
Muss jetzt nur noch überlegen, wie ich das ganze in eine Prozedure packe.
Muss jetzt nur noch überlegen, wie ich das ganze in eine Prozedure packe.
Re: Welche uses braucht GetSubDirectories?
Mit den Images?Lion hat geschrieben:Also wenn ich dein Vorschlag dazu nehme, dann sieht es schon mal gut aus.![]()
Muss jetzt nur noch überlegen, wie ich das ganze in eine Prozedure packe.
Das sollte in einem Aufwasch gehen, dann kannst du LoadRootFolderToShellTreeView vergessen.
Code: Alles auswählen
procedure TForm1.ShellTreeView1CustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
var sl: TStringList;
begin
sl := FindAllDirectories(TShellTreeView(Sender).GetPathFromNode(Node), false);
Node.HasChildren:= sl.Count > 0;
if Node.HasChildren then Node.ImageIndex:=0 else Node.ImageIndex:=1;
sl.free;
end;
Re: Welche uses braucht GetSubDirectories?
Aha... Das kann ich nicht bestätigen, bei mir arbeitet dieser Workaround völlig reibungslos, auch wenn ich viele Verzeichnisse aufmache. Aber während meiner Versuche daran hatte ich das auch öfters. Bist Du sicher, daß Du es in das Event OnCustomDraw eingehängt hast und nicht in OnCustomDrawItem oder eines der OnAdvancedCustomDraws? Genau da hatte ich das nämlich auch. Ansonsten könntest Du noch versuchen, alle Properties auf Standard zurückzustellen, da scheint es auch manchmal komische Quereffekte zu geben. Eine weitere Fehlermöglichkeit wäre, Expand / Collapse mit dem Parameter True für rekursive Aktionen aufzurufen, dann kannst Du natürlich erstmal Kaffee kochen gehen...Lion hat geschrieben:Habe es eben ausprobiert und die TreeView verhält sich irgendwie komisch. Bei sehr großen Verzeichnissen wird sie einfach leer und beim Schließen reagiert die Anwendung nicht, ist wohl im Hintergrund am arbeiten.
Kann aber auch sein, daß da Unterschiede zwischen Windows und Linux / GTK2 zuschlagen. Ich weigere mich seit 12, 13 Jahren beharrlich, Windows-Rechner auch nur anzukucken, deshalb kann ich das weder testen noch etwas dazu sagen.
Die i:=0ToCount-1-Variante hatte ich zuerst versucht, dann aber schnell wieder verworfen. Das Problem bei diesem Konstrukt ist, daß sich der Inhalt der Liste ständig ändert, sobald man irgendwas auf- und zumacht, Count wächst dabei ständig an. Und offenbar werden die Nodes dabei nicht einfach hinten angefügt, sondern eingeordnet, so daß diese Schleife im Endeffekt irgendwelche zufälligen Treffer abarbeitet und dann abbricht. Das ist jetzt bei Dir nicht der Fall, weil Du keine Expand-/Collapse-Aktionen durchführst, ich erwähne es nur mal vorsorglich.Lion hat geschrieben:Ich selber habe mir folgendes gebastelt...
Code: Alles auswählen
... for i := 0 to ShellTreeView1.Items.Count - 1 do ... ... ShellTreeView1.Items[i].ImageIndex := 0; ...
ShowButtons ist klar, das ist eine node-übergreifende TreeView-Property und eignet sich daher nicht. Und ein Fehler ist, daß Du die Stringlisten zwar en masse erzeugst, aber nicht mehr freigibst: In die i-Schleife gehört hinten also noch ein SL.Free.
Ansonsten aber ist Deine Variante gar nicht so verkehrt, nur trifft ImageIndex das Ziel nicht wirklich. Das Kästchen scheint an TTreeNode.HasChildren zu hängen, und das ist erstmal ungeprüft auf true gesetzt (das ist m.E. der interne Kern des Problems). Das hab ich jetzt mal ausprobiert, und - eh voila! - es funktioniert:
Code: Alles auswählen
procedure TForm1.UpdateShellTreeView(STV: TShellTreeView);
var
TN : TTreeNode;
SL : TStringList;
begin
STV.BeginUpdate;
TN := STV.Items.GetFirstVisibleNode;
Repeat
if (not TN.Expanded) and TN.HasChildren then
begin
SL := FindAllDirectories(STV.GetPathFromNode(TN), false);
TN.HasChildren := SL.Count > 0;
SL.Free;
end;
TN := TN.GetNextVisible;
until TN = nil;
STV.EndUpdate;
end;Gruß Rüdiger
Edit: Ah, sehe gerade, Theo hatte diesselbe Idee.
Edit2: Richtig, Theo, Deine Variante, es in OnCustomDrawItem einzuhängen, ist noch eleganter. Ich würde nur aus Laufzeitgründen noch die Bedingung if (not expanded) and HasChildren hinzufügen.
Re: Welche uses braucht GetSubDirectories?
Sag mal und wie lade ich die TreeView während der Laufzeit immer wieder neu?theo hat geschrieben:Mit den Images?Lion hat geschrieben:Also wenn ich dein Vorschlag dazu nehme, dann sieht es schon mal gut aus.![]()
Muss jetzt nur noch überlegen, wie ich das ganze in eine Prozedure packe.
Das sollte in einem Aufwasch gehen, dann kannst du LoadRootFolderToShellTreeView vergessen.
Code: Alles auswählen
procedure TForm1.ShellTreeView1CustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean); var sl: TStringList; begin sl := FindAllDirectories(TShellTreeView(Sender).GetPathFromNode(Node), false); Node.HasChildren:= sl.Count > 0; if Node.HasChildren then Node.ImageIndex:=0 else Node.ImageIndex:=1; sl.free; end;
Edit: Habe schon gefunden:
Code: Alles auswählen
ShellTreeView1.BeginUpdate;
ShellTreeView1.Root := '';
ShellTreeView1.Root := gvDirectory;
ShellTreeView1.EndUpdate;
Zuletzt geändert von Lion am Mi 4. Feb 2015, 13:36, insgesamt 2-mal geändert.