Okay, back to Topic. Es gibt eine schlechte Nachricht und eine gute:
Die Schlechte ist: Der vor einigen Tagen entwickelte Workaround mit der in
TShellTreeView.OnCustomDrawItem eingehängten Prüfung auf Unterverzeichnisse arbeitet nur dann (und selbst dann nur
einigermaßen) fehlerfrei, wenn die Property
TShellTreeView.ObjectTypes auf
otFolders, nicht aber auf
otNonFolders und/oder
otHidden gesetzt ist.
Die gute Nachricht: Es gibt dafür eine Lösung, die auch den
OnCustomDrawItem-Workaround überflüssig macht.
TShellTreeView enthält zwei zentrale Methoden für den Aufbau des Verzeichnisbaums: Die übergeordnete ist
PopulateTreeNodeWithFiles. Sie wiederum ruft
GetFilesInDir auf, wo der eigentliche Einlesevorgang stattfindet und entschieden wird, ob eine Datei bzw. ein Verzeichnis angezeigt wird oder nicht (abhängig von den
ObjectTypes-Einstellungen).
Die Property
ObjectTypes legt fest, ob die ShellTreeView nur Verzeichnisse oder (auch oder ausschließlich) Dateien anzeigen soll, und ob versteckte Verzeichnisse bzw. Dateien ebenfalls angezeigt werden sollen. Im Normalfall wird die ShellTreeView zusammen mit einer (ankoppelbaren) ShellListView verwendet, dann macht es kaum Sinn, sie etwas anderes als ausschließlich Verzeichnisse anzeigen zu lassen. Selbst die Folder-Anzeige ganz wegzuschalten, ist möglich, auch wenn das Ergebnis dann ziemlich unsinnig wird. Aber was soll's: Es gibt nun einmal diese Einstellungsmöglichkeiten, also sollten sie auch korrekt funktionieren. Doch genau da gibt es ein tieferliegendes Problem, weil TShellTreeView hier noch einen weiteren Fehler im Gebälk hat.
Zunächst: Welche Bedeutung haben die Einstellungen von
ObjectTypes eigentlich? Auf den zweiten Blick taucht da nämlich eine Schwierigkeit auf:
otFolders und
otNonFolders ist klar: Einmal sind Verzeichnisse gemeint, das andere steht für Dateien. Aber was meint
otHidden? Schließlich gibt es sowohl versteckte Directories als auch versteckte Files. Hier braucht es also eine Festlegung. Die lautet:
otHidden ist den beiden anderen Einstellungen untergeordnet und wird je nach deren Stellung interpretiert: Wenn
otFolders gesetzt ist, sorgt
otHidden dafür, daß auch versteckte Verzeichnisse angezeigt werden - und entsprechend muß
otNonFolders gesetzt sein, um zusammen mit
otHidden auch versteckte Dateien sichtbar zu machen. Einen Sonderfall stellen die Verzeichnisse "." und ".." dar, die werden unter keinen Umständen angezeigt.
Ich habe mich dafür entschieden, eine zusätzlich Funktion namens
TCustomShellTreeView.NodeHasUnbornChild zu implementieren, die aus
PopulateTreeNodeWithFiles heraus abgefragt wird. Die hat den Vorteil, daß sie nicht (wie
GetFilesInDir) das gesamte Unterverzeichnis einlesen muß, sondern abbricht, sobald ein Treffer gefunden wurde - der Nachteil ist, daß sie
GetFilesInDir in Teilen verdoppelt.
Das alleine funktioniert aber immer noch nicht befriedigend. Das Problem dabei ist, daß TShellTreeView bereits in ihrem Konstruktor
Create angewiesen wird, das
Base-Verzeichnis einzulesen. Zu diesem Zeitpunkt sind aber weder
Root noch
ObjectTypes oder sonst irgendwelche Properties aus den Ressourcen geladen. Damit das nicht schiefgeht, wird in
Create erst einmal
ObjectTypes auf
otFolders gesetzt, egal, was die Komponente eigentlich später mal anzeigen soll. Nun wird
PopulateTreeNodeWithFiles mit dieser Einstellung gleich zweimal durchlaufen,
erst dann werden die Properties aus den Ressourcen eingelesen. Und zwar zunächst
FileSortType, dann
Root und erst ganz zum Schluß
ObjectTypes. Jede dieser Property-Zuweisungen triggert
PopulateTreeNodeWithFiles (insgesamt 5-mal), nur dummerweise
ObjectTypes nicht, deren write-Pfad direkt zum Feld
FObjectTypes führt. Letztlich führt das dazu, daß die
erste Anzeige der ShelltreeView die Einstellungen von
ObjectTypes schlicht ignoriert. Das wiederum ist nicht nur lästig, sondern kann auch zu echten Fehlern führen, weil nämlich u.U. ein Verzeichnis erst gar nicht geöffnet werden kann, das eigentlich sehr wohl ChildNodes enthält. Aus diesem Grund habe ich auch noch eine zusätzliche Prozedur
SetObjectTypes hinzugefügt und die Zuweisung der Property
ObjectTypes darauf umgebogen. Dort wird
PopulateTreeNodeWithFiles ein sechstes Mal getriggert, bevor erstmals was auf dem Bildschirm zu sehen ist - was gewiß keine schöne Lösung ist, aber so funktioniert das Ganze nun immerhin (jedenfalls soweit ich sehen kann).
Hier nun die Modifikationen der Unit ShellCtrls.pas:
Code: Alles auswählen
{*************** Modifikationen der TCustomShellTreeView-Deklaration: ***************}
TCustomShellTreeView = class(TCustomTreeView)
private
...
{+} procedure SetObjectTypes(AValue : TObjectTypes);
protected
{ Other methods specific to Lazarus }
{+} function NodeHasUnbornChild(ANode : TTreeNode) : Boolean;
...
public
{ Properties }
{- property ObjectTypes: TObjectTypes read FObjectTypes write FObjectTypes; }
{+} property ObjectTypes: TObjectTypes read FObjectTypes write SetObjectTypes;
...
end;
{*************** Zusätzliche Prozedur TCustomShellTreeView.SetObjectTypes: ***************}
procedure TCustomShellTreeView.SetObjectTypes(AValue : TObjectTypes);
begin
if FObjectTypes = AValue then Exit;
FObjectTypes := AValue;
Refresh(nil); // Refresh with Root
end;
{*************** Zusätzliche Funktion TCustomShellTreeView.NodeHasUnbornChild: ***************}
function TCustomShellTreeView.NodeHasUnbornChild(ANode: TTreeNode): Boolean;
var
DirInfo: TSearchRec;
FindResult: Integer;
IsDirectory,
IsValidDirectory,
IsHidden,
HasChildDir,
HasChildFile,
HasHiddenChildDir,
HasHiddenChildFile : Boolean;
begin
Result := false;
FindResult := FindFirstUTF8(IncludeTrailingPathDelimiter(GetPathFromNode(ANode)) + AllFilesMask,
faAnyFile and faDirectory, DirInfo);
while (FindResult = 0) and (not Result) do
begin
IsDirectory := (DirInfo.Attr and FaDirectory = FaDirectory);
IsValidDirectory := IsDirectory and (DirInfo.Name <> '.') and (DirInfo.Name <> '..');
IsHidden := (DirInfo.Attr and faHidden = faHidden);
{$IFDEF Unix}
if (DirInfo.Name<>'') and (DirInfo.Name[1]='.') then IsHidden := true;
{$ENDIF}
HasChildDir := IsValidDirectory and (not IsHidden) and (otFolders in FObjectTypes);
HasChildFile := (not IsDirectory) and (not IsHidden) and (otNonFolders in FObjectTypes);
HasHiddenChildDir := IsValidDirectory and IsHidden and
(otFolders in FObjectTypes) and (otHidden in FObjectTypes);
HasHiddenChildFile := (not IsDirectory) and IsHidden and
(otNonFolders in FObjectTypes) and (otHidden in FObjectTypes);
if HasChildDir or HasChildFile or HasHiddenChildDir or HasHiddenChildFile then Result := true
else FindResult := FindNextUTF8(DirInfo);
end;
FindCloseUTF8(DirInfo);
end;
{*************** Modifikationen der Funktion TCustomShellTreeView.PopulateTreeNodeWithFiles: ***************}
function TCustomShellTreeView.PopulateTreeNodeWithFiles(ANode: TTreeNode; ANodePath: string): Boolean;
var
....
begin
...
for i := 0 to Files.Count - 1 do
begin
NewNode := Items.AddChildObject(ANode, Files.Strings[i], nil); //@Files.Strings[i]);
{- NewNode.HasChildren := Files.Objects[i] <> nil; // This marks if the node is a directory }
{+} NewNode.HasChildren := (Files.Objects[i] <> nil) and NodeHasUnbornChild(NewNode);
end;
...
end;
Ich bin noch etwas unentschlossen, ob ich daraus einen Patch machen und ihn in den Bug-Tracker geben soll. Denn eigentlich hätte die Komponente eine grundlegendere Revision verdient. Wie seht Ihr das?
Auch müßte ich erstmal die aktuelle Entwicklerversion installieren, mein Stand ist Lazarus 1.2.4. Vor allem aber habe ich keine Möglichkeit, diese Modifikation unter Windows zu testen. Ich würde das weitere Vorgehen daher auch von Eurer Reaktion abhängig machen, insbesondere von einer Rückmeldung, ob das Ganze auch unter Windows fehlerfrei funktioniert. Deshalb hier noch das Testprogramm (das auch die modifizierte ShellCtrls.pas enthält):
Gruß Rüdiger