Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - ProgressBar laufen lassen, während ein Prozess läuft?


del1312 - Fr 20.08.10 12:25
Titel: ProgressBar laufen lassen, während ein Prozess läuft?
Hallo Leute,

bastel grad an einem kleinen Tool, welches mir auf der Festplatte bestimmte Daten sucht. Das ganze mach ich 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:
procedure GetFilesInDirectory(Directory: stringconst Mask: string;
List: TStrings;
WithSubDirs, ClearList: Boolean);

procedure ScanDir(const Directory: string);
var
  SR: TSearchRec;
begin
  if FindFirst(Directory + Mask, faAnyFile and not faDirectory, SR) = 0 then try
    repeat
      List.Add(Directory + SR.Name)
    until FindNext(SR) <> 0;
  finally
    FindClose(SR);
  end;

  if WithSubDirs then begin
    if FindFirst(Directory + '*.*', faAnyFile, SR) = 0 then try
      repeat
        if ((SR.attr and faDirectory) = faDirectory) and
           (SR.Name <> '.'and (SR.Name <> '..'then
          ScanDir(Directory + SR.Name + '\');
      until FindNext(SR) <> 0;
    finally
      FindClose(SR);
    end;
  end;
end;

begin
  List.BeginUpdate;
  try
    if ClearList then
      List.Clear;
    if Directory = '' then Exit;
    if Directory[Length(Directory)] <> '\' then
      Directory := Directory + '\';
    ScanDir(Directory);
  finally
    List.EndUpdate;
  end;
end;


Aufrufen tue ich das dann hier:

Delphi-Quelltext
1:
GetFilesInDirectory(Pfad, '*-2010_*', Listbox1.Items, True, True);                    


Jetzt meine Frage, da es manchmal doch ne Weile dauert möchte ich gerne so eine ProgressBar einbauen.
Nur weiss ich nicht so recht wie ich die so einbaue das sie während des Prozesses läuft.
Hatte das mit einem Timer probiert, aber der schein auch nicht in Hintergrund weiter zulaufen während
die HDD durchsucht wird:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
procedure TForm1.Button1Click(Sender: TObject);
begin
Pfad:=Edit1.Text;
Timer1.Enabled:=true;
ProgressBar1.Visible:=true;
GetFilesInDirectory(Pfad, '*-2010_*', Listbox1.Items, True, True);
listcount:=ListBox1.Count;
Timer1.Enabled:=false;
ProgressBar1.Visible:=false;
ProgressBar1.Position :=0;
ShowMessage('Es wurden: '+ IntToStr(listcount)+ ' passende Datensätze gefunden!');
end;


und der im Timer stand das:


Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
ProgressBar1.Position :=ProgressBar1.Position +1;
ProgressBar1.Update ;
end;


Ich vermute ich muss

Delphi-Quelltext
1:
GetFilesInDirectory(Pfad, '*-2010_*', Listbox1.Items, True, True);                    

irgendwie in eine Schleife bauen oder? Kann mir da bitte einer helfen oder nen Tipp geben?

Noch ne Frage zu der ProgressBar. Wie kann ich die eigentlich so einrichten das sie auch korrekt
den Status anzeigt? Also ich meine irgendwo muss das errechnet werden, so und soviel % sind bereits abgearbeitet damit
die Bar überhaupt richtig anzeigt oder?

DANKE schon mal für eure Hilfe!


bummi - Fr 20.08.10 12:37

Du könntest GetFilesInDirectory eine Paramter eines ProgressDialogs mitgegeben, wenn dieser ASSIGNED ist in der Schleife updaten.


ALF - Fr 20.08.10 13:24

Hi, dazu müsstest Du aber vorher wissen, wie viel Dateien es auf Deiner Festplatte gibt!
Sonst kannst du keine prozentuale Anzeige machen wie viel oder wie lange es noch dauern wird.

Gruss Alf


bummi - Fr 20.08.10 13:40

Geht doch .... 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:
81:
82:
83:
84:
85:
procedure GetFilesInDirectory(Directory: stringconst Mask: string;
List: TStrings;
WithSubDirs, ClearList: Boolean;PG:TProgressbar);
var
    Cur:TCursor;

Function CountDir(const Directory: string):Integer;
var
  SR: TSearchRec;
begin
  Result := 0;

  if FindFirst(Directory + Mask, faAnyFile and not faDirectory, SR) = 0 then try
    repeat
      inc(result);
    until FindNext(SR) <> 0;
  finally
    FindClose(SR);
  end;
  if WithSubDirs then begin
    if FindFirst(Directory + '*.*', faAnyFile, SR) = 0 then try
      repeat
        if ((SR.attr and faDirectory) = faDirectory) and
           (SR.Name <> '.'and (SR.Name <> '..'then
           Result := Result + CountDir(Directory + SR.Name + '\');
      until FindNext(SR) <> 0;
    finally
      FindClose(SR);
    end;
  end;
end;

procedure ScanDir(const Directory: string);
var
  SR: TSearchRec;
begin


  if FindFirst(Directory + Mask, faAnyFile and not faDirectory, SR) = 0 then try
    repeat
      List.Add(Directory + SR.Name);
      if Assigned(PG) then
        begin
            pg.Position := List.Count;
        end;

    until FindNext(SR) <> 0;
  finally
    FindClose(SR);
  end;

  if WithSubDirs then begin
    if FindFirst(Directory + '*.*', faAnyFile, SR) = 0 then try
      repeat
        if ((SR.attr and faDirectory) = faDirectory) and
           (SR.Name <> '.'and (SR.Name <> '..'then
           ScanDir(Directory + SR.Name + '\');
      until FindNext(SR) <> 0;
    finally
      FindClose(SR);
    end;
  end;
end;

begin
  Cur := Screen.Cursor;
  try
  Screen.Cursor := crHourGlass;
  if Assigned(PG) then pg.State := pbsNormal;
  List.BeginUpdate;

    if ClearList then
      List.Clear;
    if Directory = '' then Exit;
    if Directory[Length(Directory)] <> '\' then
      Directory := Directory + '\';
    if Assigned(PG) then pg.Max := CountDir(Directory);
    ScanDir(Directory);
  finally
    List.EndUpdate;
    if Assigned(PG) then pg.State := pbsPaused;

  end;
  Screen.Cursor := Cur;
end;


del1312 - Fr 20.08.10 13:54

DANKE! Also ich habs mal eingebaut und hier noch die Progressbar1 eingetragen, das war doch richtig oder?

GetFilesInDirectory(Pfad, '*-2010_*', Listbox1.Items, True, True, Progressbar1);

Also er macht erstmal was, aber warum wird die Bar gelb und so richtig flüssig ist es auch nicht, bzw erst kommt
nichts und dann ist halb und dann voll. Geht warhscheinlich nicht anders oder? Na erstmal vielen DANK bummi!!!!


ALF - Fr 20.08.10 13:56

Jo, user profile iconbummi ,damit hat er aber leider keine Progressanzeige wie lange es noch dauert :wink:

Problem mit Progessbar ist, das Du vorher erst einmal die gesammte Platte scannen must um festzustellen wie viel Dateien da drauf sind, um anzuzeigen wie lange es noch dauern wird!
Andere Möglichkeit, etwas schneller, den belegten Platz auf der Festplatte zu nehmen und dann beim Scannen den belegten Platz, (nicht Dateigrösse), den eine Datei einimmt, zu addieren um somit eine Progressanzeige zu verwirklichen! Ob dies aber sauber ist weis ich nicht!

Gruss Alf


del1312 - Fr 20.08.10 13:59

Hm ok dann muss ich das wohl weglassen, schade. Vielen Dank für eure Hilfe


Tankard - Fr 20.08.10 14:24

hallo,

ruf in der schleife ab und zu mal

Application.ProcessMessages

auf. sonst werden die messages nicht abgearbeitet und deine ganze application scheint für den benutzer zu stehen. das bedeutet auch das die progressbar sich nicht bewegt und erst nach beenden der suche neu gezeichnet wird.

gruss

tankard


ALF - Fr 20.08.10 14:39

Warum so schnell aufgeben? Wenn du nicht unbedingt im Windows Ordner selbst was suchen willst, sondern nur in den Bereichen, Programme und alles was für den User gültigkeit hat, pro Partition/Fetsplatte ist auch dieses sehr flink!
Zumal man ja unter umständen den Scann für die Anzahl der vorhandenen Dateien, ja auch in einen Thread auslagern kann (der muss ja nur die Dateien Zählen nix vergleichen) und dann bei Deinem eigentlichen Suchvorgang die anzahl der Dateien für Deine Progressbar zu aktualliesieren! Ist nur so eine Idee 8)

Gruss Alf


Xion - Fr 20.08.10 15:52

Wenn ich nicht grad total bescheurt bin, dann braucht bummies Code DOPPELT so lange wie der normale Code, weil er zweimal alle Dateien sucht...also das wäre ja ein Verbrechen :D

:arrow: Das einfachste wäre wohl so eine tolle Windows-"Ich bin noch nicht abgestürzt"-Leiste hinzumachen, die einfach immer durchläuft
In der Machart:
http://www.linnekogel.de/images/layout/ladebalken.gif

Das ist immer gut wenn man garkeine Ahnung hat was los ist. Super ist, wenn das Programm abgestürzt ist und das Ding dreht sich trotzdem weiter :P

:arrow: Dann könntest du, wenn du immer die ganze Platte durchsucht (was ich nicht empfehlen würde) auch die Zeit messen, die du beim ersten mal brauchst. Beim zweiten mal Suchen verwendest du die als ProgressBar.Max. Und du misst wieder die Zeit, mittelst die beiden Zeiten usw. Vorteil: Braucht garkeine Zeit und nach paarmal Suchen sollte es einigermaßen passen.

:arrow: Wenn du die Dateien erst zählen willst, ist das meiner Meinung nach Quatsch...denn in dem Moment bist du ja dann schon fertig, da du ja nur die Dateien suchst. Also in dem Moment wo du sie mitzählst, warst du ja schon bei ihr. Anders ist es, wenn du die Dateien z.B. kopierst. Dann fällt die Zeit, die du zum zählen verwendest, nicht so ins Gewicht, da das Kopieren viel länger dauert.


del1312 - Fr 20.08.10 17:08

Stimmt das einfach mit ner Windows-"Ich bin noch nicht abgestürzt"-Leiste ist ne super Idee, das werd ich mir glatt mal anschauen, danke!


ALF - Fr 20.08.10 17:18

user profile iconXion hat folgendes geschrieben Zum zitierten Posting springen:
:arrow: Das einfachste wäre wohl so eine tolle Windows-"Ich bin noch nicht abgestürzt"-Leiste hinzumachen, die einfach immer durchläuft
In der Machart:
http://www.linnekogel.de/images/layout/ladebalken.gif
Dann könnte er auch den Cursor dafür setzten, wie @Bummi es vorgeschlagen hat! :wink:

user profile iconXion hat folgendes geschrieben Zum zitierten Posting springen:
Das ist immer gut wenn man garkeine Ahnung hat was los ist. Super ist, wenn das Programm abgestürzt ist und das Ding dreht sich trotzdem weiter :P
Halt der Nachteil! :shock:

user profile iconXion hat folgendes geschrieben Zum zitierten Posting springen:
:arrow: Dann könntest du, wenn du immer die ganze Platte durchsucht (was ich nicht empfehlen würde)
Würd ich auch nicht empfehlen, nur manchmal brauch man es halt :wink:

user profile iconXion hat folgendes geschrieben Zum zitierten Posting springen:
auch die Zeit messen, die du beim ersten mal brauchst. Beim zweiten mal Suchen verwendest du die als ProgressBar.Max. Und du misst wieder die Zeit, mittelst die beiden Zeiten usw. Vorteil: Braucht garkeine Zeit und nach paarmal Suchen sollte es einigermaßen passen.
Auf sowas würde noch nicht mal ich kommen 8)


user profile iconXion hat folgendes geschrieben Zum zitierten Posting springen:
:arrow: Wenn du die Dateien erst zählen willst, ist das meiner Meinung nach Quatsch..

Nein, er soll ja erst vor den Suchen Zählen, wenn er mit Progressbar arbeiten will, nicht zur gleichen Zeit wenn er sucht. Habe ich aber deutlich geschrieben :?
Dabei hab ich ihm 3 Möglichkeiten aufgezählt! Ne 4. währe, nur den Inhalt des jeweiligen Ordner zu zählen und dann im Ordner zu suchen. Wenn man mit Progressbar arbeiten will!!!

Das bleibt aber jedem selbst überlassen :P
Gruss Alf


delfiphan - Fr 20.08.10 20:40

Längere Operationen auf dem UI Thread laufen zu lassen ist für mich zu mindest Tabu. Ich würde die Suche in einem Thread machen. Dort kannst den jeweils den aktuellen Stand über ein Property auslesbar machen (die muss threadsafe sein). Der Progressbar liest dann z.B. über einen Timer regelmässig den Wert ab. Falls der Progress unbekannt desto einfacher (dann lässt du die Geschichte einfach weg).

Beim Listbox1.Items müsstest du bei Threads aufpassen, da diese nicht threadsafe ist. Da du ja aber sowieso BeginUpdate/EndUpdate verwendest bist du offenbar nicht am Zwischenresultat interessiert. D.h. dann würde ich die Methode eine eigene TStringList erzeugen lassen und diese über einen Callback zurückgeben.


elundril - Fr 20.08.10 20:57

Es gibt Dinge die "darf" man einfach nicht in einen Thread auslagern, weil es einfach vom Design her unnötig ist. Sieh Luckys Thread-Tutorial [http://www.michael-puff.de/Programmierung/Delphi/Tutorials/Threads/HTML/Threads_mit_Delphise3.html#x9-100002.3].


delfiphan - Fr 20.08.10 21:10

Die Lösung mit Application.ProcessMessages oder einer blockierenden UI würde ich absolut davon abraten. Das Design mit Threads ist State of the Art. Ich sag ja nicht überall, aber Festplatte durchsuchen ist dafür bestens geeignet.

In neuen Technologien wie Silverlight gibt es für längere Operationen überhaupt keine Blocking Calls mehr sondern ausschliesslich asynchrone Pendants.

Gutes Design ist, wenn der UI Thread zu keiner Zeit stillsteht. Wenn du die UI eine Sekunde lang blockierst hast du was falsch gemacht. Wenn sie 5 Sekunden still steht heisst es seitens Windows nur noch "Not Responding".


Tranx - Sa 21.08.10 17:39

Vielleicht ist mal das alte DIR ganz sinnvoll zur Ermittlung der Dateienanzahl:

DIR "Pfad" /S >"Textdatei"

dann steht in der Textdatei die Anzahl der Dateien in der vorletzten Zeile.

Du musst dan nur folgendes Aufrufen:

Habe das Starten in eine Prozedur Progstarten geschrieben:

Ich glaube Uses Windows muss dazu in der Unit sein, sionst funktioniert das Ganze nicht.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure ProgStarten(const DateiName: Stringconst Parameter: String=''const immer : boolean = FALSE);
var
  Erg    : integer;
begin
  if not(FileExists(Dateiname)) and not(immer) then begin
    Fehler('Das Programm ['+Dateiname+'] existiert nicht! In den Optionen bitte korrigieren.');
    Exit;
  end;
  Erg := ShellExecute(Application.Handle,'open',PChar(DateiName),PChar(Parameter),PChar(ExtractFilePath(DateiName)),SW_SHOW);
  if Erg<32 then Mitteilung('Beim Start des Programms '+Dateiname+' ist ein Fehler aufgetreten!');
end;

Du rufst dann das Dir mit

Delphi-Quelltext
1:
Progstarten('DIR','/S > '+Dateiname,TRUE);                    
auf

Dann musst Du nur die vorletzte Zeile der Textdatei einlesen und daraus de Anzahl der Dateien auslesen.

Für die Restzeit brauchst Du nur die Anzahl, die derzeitige Position, die Startzeit und die aktuelle Zeit

Delphi-Quelltext
1:
Restzeit := (now - Startzeit)*(Anzahl - n)/Anzahl;                    

Das gibst Du unter dem Progressbar aus.

Am Besten Du nimmst die angehängte Unit für den Progressbar. Dann erscheint dieser in einem Extrafenster und der Vorgang kann jederzeit abgebrochen werden.

Hierzu wird die globale Variable PROCAbbruch auf TRUE gesetzt.

Den Fortschritt akitivierst Du mit

Delphi-Quelltext
1:
FortschrittBeginnen(Anzahl, TRUE/FALSE);                    

innerhalb der Schleife:

Delphi-Quelltext
1:
FortschrittAktualisieren(Text);                    

und am Ende:

Delphi-Quelltext
1:
FortschrittBeenden;                    

Über Aufzählen von n oder I oder sonst was - kein Gedanke daran. Steht alles in der Unit.
Nur am Anfang einmal die Gesamtzahl vorgeben!

Ich habe es mit Delphi 5 geschrieben. Hoffentlich funktioniert dies auch mit anderen Versionen.

Moderiert von user profile iconNarses: Delphi-Tags hinzugefügt


jaenicke - Sa 21.08.10 17:58

:autsch:

Was hat es für einen Sinn einen Kommandozeilenbefehl zu benutzen und die Ausgabe auszulesen? Der Effekt ist nur, dass dein Formular erst recht nicht mehr reagiert, weil du keine Möglichkeit hast während der Ausführung und dem Warten darauf etwas anderes zu machen oder Rechenzeit freizugeben.

Klar kannst du dann das wiederum in einen Thread legen, aber das kannst du dann auch gleich mit dem Delphicode zur Suche machen.

Ja, und dann willst du die Anzahl der gefundenen Dateien für den Fortschritt der erneuten (!!) Suche benutzen?!? :shock:
Du hast dann doch schon die Liste der Dateien...

Ja, und zuguterletzt:
"dir" ist keine Exe, dementsprechend musst du den Befehl auch als Parameter an cmd übergeben und kannst den Befehl nicht als Dateiname an ShellExecute übergeben...


Delete - Sa 21.08.10 21:39

Ich habe so etwas mal realisiert: http://www.michael-puff.de/Programmierung/Delphi/Units -> MpuDriveTools.pas
Mit der Unit geht so was. Allerdings sollte man sich im Klaren sein, dass man immer Performance Einbußen hat.


ssb-blume - So 22.08.10 09:10

Hallo,
habe mal eine sehr alte Version für dieses Problem erstellt, siehe Anhang (.ZIP)
Es sind alle Quellen dabei, auch eine kurze Doc.
Ich hoffe, Du kannst damit etwas anfangen!

Hansi


Gerd Kayser - Di 24.08.10 22:35

user profile icondel1312 hat folgendes geschrieben Zum zitierten Posting springen:
Stimmt das einfach mit ner Windows-"Ich bin noch nicht abgestürzt"-Leiste ist ne super Idee, das werd ich mir glatt mal anschauen, danke!
Ich würde einfach eine Art Statistik ausgeben. So in der Art:

Delphi-Quelltext
1:
2:
3:
Aktuelles Verzeichnis: c:\programme\borland
Anzahl Dateien: 2197
gefundene Dateien: 12
Dazu vielleicht noch eine kleine Animation. Falls sich an das Suchen der Dateien noch ein weiterer (zeitintensiver) Lauf zum Verarbeiten der gefundenen Dateien anschließt, kann man dann eine ProgressBar verwenden.


jaenicke - Mi 25.08.10 05:51

Wobei wie schon gesagt wurde zu bedenken ist, dass die Anzeige den Prozess deutlich verlangsamen kann. Insbesondere wenn jede Datei als Fortschritt angezeigt wird. Dann kann das ganze schon mal z.B. 50% länger dauern.

Selbst stark optimiert (minimierte Aufrufanzahl, zeitliche Abstände zwischen Anzeigeschritten, ...) sind es bei mir bei einem aktuellen Projekt noch ca. 4%, die das ganze durch die Fortschrittsanzeige langsamer wird.