der Windows Explorer listet Dateien immer schneller als mein Programm. Die Frage ist eigentlich kurz und einfach: Wie könnte man die Auflistung noch verschnellern ? Der folgende Code sollte sogar - abgesehen von so Kleinigkeiten wie das Auslesen des Icons eines Dateityps - unter Linux laufen. Habe es allerdings noch nicht getestet. Bietet Lazarus noch irgendwelche Funktionen die Delphi nicht bietet ? Der Code wird später noch unter Linux zum Einsatz kommen, daher hielt ich es für sinnvoll, mit dem SearchRec zu arbeiten, da dieses wie ich irgendwo gelesen hatte auch mit Linux benutzbar sei.
Der Code ist weder komplex noch lang. Die Kommentare lassen es viel aussehen, erklären allerdings alles ausführlich.
Code: Alles auswählen
type
TItem = record
Pfad, Name, Typ, Alter : String;
ImageIndex : Integer;
end;
TDynItemArray = Array of TItem;
TRazFileManager = class
private
LV : TListView;
Sep : String; // / oder \ Beispiel: /mnt/datene/blubb/asd oder E:\blubb\asd.
Ext : TStrings; // Stringlist mit allen Dateiendungen, von denen bisher ein Icon geladen worden ist.
Dirs, Files, DirsFiles : TDynItemArray; // Dirs=Array mit Verzeichnissen, Files=Array mit Dateien, DirsFiles=Sortierter Zusammenschluss von Dirs und Files.
Img16, Img32 : TImageList; // In Img16 sind die kleinen, in Img32 die etwas größeren Icons.
Pfad : String; // Um später besser navigieren zu können schadet es nicht, den Pfad parat zu haben, statt ihn ständig ermitteln zu müssen.
public
Filter : TStrings; // Suchfilter.
procedure Liste(Directory : String; Recursive, ClearList : Boolean); // Das ist der kleine Algorithmus.
end;
implementation
// Als Parameter benötigt man das Verzeichnis (Standard "root"), ob rekursiv gesucht werden soll (Standard "False") und ob die ListView wieder geleert werden soll (Standard "True").
procedure TRazFileManager.Liste(Directory : String; Recursive, ClearList : Boolean);
// Durchsuchen eines einzelnen Verzeichnisses
procedure ScanDir(Directory : String);
var SR : TSearchRec;
lIcon : TIcon; // Ein temporäres Icon welches zur Verarbeitung leider nötig ist.
fTyp : String; // Das ist die wahre Dateiendung (zum Beispiel: .mp3). Der TItem.Typ ist hingegen die Beschreibung eines Typen (zum Beispiel: MPEG3).
IconIndex : Integer; // IconIndex darf nicht mit ImageIndex verwechselt werden, es dient zur Zuordnung des Icons: Ist es schon geladen ?.
ViewSize : Integer; // Die Funktion GetExtIcon liest das Icon eines Dateityps/Programms aus. Da die Funktion intern prüft, welcher ViewStyle gerade benutzt wird, ist es zwecklos, dies hier ein paar Zeilen später noch einmal zu prüfen. Man muss schließlich wissen, in welche ImageList das Icon soll. Daher gibt es die Variable ViewSize, welche die GetExtIcon zurückgibt. Dies erspart ein paar ors.
begin
if FindFirst(Directory+'*.*', faAnyFile and not faDirectory, SR) = 0 then
try
repeat
// Dateisuche: Passt der Filter ?
if (Filter.IndexOf(ExtractFileExt(SR.Name)) <> -1) or (Filter.Count < 1) then
begin
SetLength(Files, Succ(Length(Files)));
// Dateiendung der aktuellen Datei bestimmen.
fTyp := LowerCase(ExtractFileExt(Directory+SR.Name));
// Ab jetzt ist Pfad nicht der in private deklarierte Pfad mehr, sondern der in TItem deklarierte. Denn Files ist ein TDynItemArray.
with Files[High(Files)] do
begin
// Pfad setzen.
Pfad := Directory;
// Name bestimmen und setzen.
Name := ExtractFileName(Directory+SR.Name);
// Dateibeschreibung bestimmen und setzen.
Typ := GetExtDes(fTyp, False);
// Letzte Dateiänderung auslesen.
Alter := IntToStr(FileAge(Directory+SR.Name));
// Weitere Dateiinformationen folgen in Zukunft noch, aber ich hätte sie zur Übersicht auch so weggelassen.
// Wurde das Icon dieser Dateiendung bereits geladen ?
IconIndex := Ext.IndexOf(fTyp);
if IconIndex = -1 then
begin
lIcon := TIcon.Create;
if fTyp = '.exe' then // Derzeit noch etwas windowsspezifisch, ich werde das auch noch ändern.
begin
// Da die Programmicons nur sehr grundlegend etwas mit der Dateiendung zu tun haben (Ausnahme: DOS-Programme haben kein Icon, bekommen daher ein festes), muss man Programme anders behandeln. Es wird daher der komplette Pfad+Dateiname+Dateiendung hinzugefügt. Wenn man zwei mal in unterschiedlichen Ordner eine a.exe hat. Heißt das nicht, dass es das selbe Programm ist. Daher gehe ich hier auf Nummer sicher. Ich muss alledings gestehen, dass ich die Stringlist Ext etwas vergewaltige, da keine Dateiendung hinzugefügt wird, aber das ist eigentlich egal.
Ext.Add(Directory+SR.Name);
// Icon auslesen mit dem kompletten Pfad+Dateiname+Dateiendung, sowie eine temporäre Zuweisung auf das schon oben erzeugte lokale Icon (lIcon). Das False sagt nur, dass die Datei nicht existieren muss, was die beiden Angaben LV.ViewStyle und ViewSize für einen Sinn haben, ist oben bei der Deklaration von ViewSize erklärt.
GetExtIcon(Directory+SR.Name, lIcon, False, LV.ViewStyle, ViewSize)
end else
begin
// Da abgesehen von Programmen die Icons von der Dateiendung abhängen, kann man hier lockerer arbeiten.
Ext.Add(fTyp);
GetExtIcon(fTyp, lIcon, False, LV.ViewStyle, ViewSize);
end;
// Wie bei der Deklaration von ViewSize beschrieben, gibt GetExtIcon die größe des Icons zurück. Daher wird hier übersichtlich geprüft, welche Imagelist befüllt werden muss.
if ViewSize = 16 then
begin
// Oben wurde das Icon auf lIcon zugewiesen oder eher gesagt gezeichnet.
Img16.AddIcon(lIcon);
// Der ImageIndex des ListView-Eintrags ist logischerweise der höchste der Imagelist.
ImageIndex := Pred(Img16.Count);
end else
if ViewSize = 32 then
begin
Img32.AddIcon(lIcon);
ImageIndex := Pred(Img32.Count);
end;
lIcon.Free;
end else
// Wenn in Ext die Dateiendung doch gefunden wurde, muss sie nicht ausgelesen werden, daher nimmt man einfach den IconIndex+ImgAL. AL steht für Anfangslänge. Ich weise schon ganz am Anfang Icons wie Datenträger, CDROM, Diskette usw. zu. Damit das dann alles relativ automatisch abläuft, habe ich diese Variable eingeführt, welche einfach draufaddiert werden muss.
ImageIndex := IconIndex+ImgAL;
end;
end;
until
FindNext(SR) <> 0;
finally
FindClose(SR);
end;
if FindFirst(Directory+'*.*', faAnyFile, SR) = 0 then
try
repeat
// Ist es ein Verzeichnis ?
if ((SR.Attr and faDirectory) = faDirectory) and (SR.Name <> '.') and (SR.Name <> '..') then
begin
SetLength(Dirs, Succ(Length(Dirs)));
with Dirs[High(Dirs)] do
begin
// Pfad setzen.
Pfad := Directory;
// Name bestimmen und setzen.
Name := ExtractFileName(Directory+SR.Name);
// Beschreibung setzen
Typ := 'Verzeichnis';
// Vielleicht fällt es jemanden auf: Bei der Dateisuche wurde auch das Änderungsdatum der Datei (FileAge) hinzugefügt, hier nicht. Gibt es eine Möglichkeit, das Änderungsdatum eines Ordners zu ermitteln ?
// Die Verzeichnisse werden bei dem zur Übersichltichkeit weggelassenen Konstruktur als letzte Icons geladen. Daher kann ich logischerweise einfach ImgAL setzen.
ImageIndex := ImgAL;
end;
// Bei einer Rekursion ist die Prozedur noch nicht fertig.
if Recursive then
ScanDir(Directory+SR.Name+Sep);
end;
until
FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
var i : Integer;
begin
// Bei jeder Suche muss alles abgesehen von der Stringlist Ext zurückgesetzt werden.
SetLength(Dirs , 0);
SetLength(Files, 0);
SetLength(DirsFiles, 0);
// Root heißt in dem Fall Übersicht der Datenträger.
if LowerCase(Directory) = 'root' then
begin
// Wenn man einen leeren Pfad hat, kann es Ärger geben, daher setze ich ihn hier.
Pfad := 'root';
GetDrives(LV);
end else
begin
// Zur besseren Performance.
LV.Items.BeginUpdate;
try
if ClearList then
LV.Items.Clear;
if not DirectoryExists(Directory) then
Exit;
if Directory[Length(Directory)] <> Sep then
Directory := Directory+Sep;
// Und los geht die Suche mit der oben gezeigten und erklärten ScanDir.
ScanDir(Directory);
finally
// Falls es in einem Verzeichnis keine Unterverzeichnisse gibt, diese Absicherung.
if High(Dirs) > 0 then
// Ergebnis ist ein alphabetisch sortiertes Array.
QuickSort(Dirs, 0, High(Dirs));
// Erst die Ordner hinzufügen. In ScanDir werden alle Informationen in das TDynItemArray geschrieben, daher können diese hier flott wieder ausgelesen werden.
for i := 0 to High(Dirs) do
begin
with LV.Items.Add do
begin
Caption := Dirs[i].Name;
SubItems.Add(Dirs[i].Typ);
ImageIndex := Dirs[i].ImageIndex;
end;
// Damit man später auch bei Doppelklick (zur Übersichtlichkeit weggelassen) keinen Ärger bekommt, wird das Array DirsFiles nun in sortierter Form befüllt. DirsFiles ist daher KEIN direkter Zusammenschluss aus dem in ScanDir entstandenen Dirs und Files. Es ist ein direkter Zusammenschluss aus den beiden eben SORTIERTEN Arrays Dirs und Files.
SetLength(DirsFiles, Succ(Length(DirsFiles)));
DirsFiles[High(DirsFiles)] := Dirs[i];
end;
// Der bei finally beginnende Code wiederholt sich hier noch einmal, allerdings auf Dateien statt Ordner bezogen. Ich kann ihn leider nicht mehr hinschreiben, da sonst dieser Code zu lange wäre und am Ende was von der Codebox weggeschnitten würde.
[...]
// Was man öffnet muss man schließen.
LV.Items.EndUpdate;
end;
end;
end;