![]() |
Progressbar um Loadfromfile fortschritt anzuzeigen.
Hallo,
(hoffentlich ist das hier das richtige Forum dafür) ich schreibe gerade eine Anwendung in die ich (mitunter sehr große >200Mb-500Mb) Dateien in eine TStringList laden werde. Bei Dateigrößen über 100Mb kann das aber schonmal paar Sekündchen dauern und deswegen wollte ich in diesem Fall eine Progressbar laufen lassen. Habe jetzt aber nach ca. 3 Stunden Suchen und Googeln den Eindruck, das das ganz schön knifflig zu sein scheint. (oder so trivial, das es keiner Rede wert ist??) Jedenfalls werde ich nicht wirklich schlau wie ich da herangehen kann. Die hier gefundenen Ansätze zuerst die Zeilenzahl zu ermitteln und danach beim einlesen mitzuzählen funktionieren wohl eher nicht, oder? Infos: - ich verwende Turbo Delphi ohne samples (also kein TGauge) Danke schonmal für eure Hilfe! |
Re: Progressbar um Loadfromfile fortschritt anzuzeigen.
Du bräuchtest einen Stream
![]() Hier die schrittweise Erklärung. Im Moment rufst du ja folgende Methode auf:
Delphi-Quellcode:
Im nächsten Schritt erzeugst du Dir einen eigenen FileStream:
stringliste.LoadFromFile(dateiname);
Delphi-Quellcode:
Damit ist aber noch nichts gewonnen.
var
fs : TFileStream; begin fs := TFileStream.Create(dateiname, ...); stringliste.LoadFromStream(fs); fs.Free; Ein Stream Decorator ist ein Klasse, die von TStream abgeleitet ist und die Aufrufe and Read() und Write() einfach nur an einen anderen Stream durchreicht. Und hier kann man eingreifen und jeden einzelnen Read() mitbekommen:
Delphi-Quellcode:
Jetzt fehlt natürlich noch die Klasse TStreamDecorator.
var
fs : TFileStream; decorator : TStreamDecorator; begin fs := TFileStream.Create(dateiname, ...); decorator := TStreamDecorator.Create(fs); Progressbar1.Max := fs.size; decorator.OnRead := OnReadHandler; stringliste.LoadFromStream(decorator); decorator.Free; fs.Free; Irgendwo in den Tiefen der DP liegt der Code zu so einer Klasse (find' ich nur im Moment nicht). In der JCL gibt es auch schon fertige Stream Decorator Klassen, die du verwenden könntest. (Unit JclStream, Klasse TJclEventStream) Oder einer der Mitleser schüttelt die Klasse aus dem Ärmel... |
Re: Progressbar um Loadfromfile fortschritt anzuzeigen.
Eine andere Möglichkeit wäre die Datei zeileweise ein zu lesen mit AssignFile, ReadLn, etc.
Delphi-Quellcode:
var
Txtdatei: Textfile; Zeile: String; begin AssignFile(Txtdatei, 'Hallo.txt'); Reset(Txtdatei); Readln(Txtdatei, Zeile); //Progressbar erhöhen CloseFile(Txtdatei); end; |
Re: Progressbar um Loadfromfile fortschritt anzuzeigen.
Tut mir Leid, aber diese Streamdekoratoren bringen nicht wirklich, denn eine StringList ließt erstmal den gesamten Stream oder die Datei ein, seit D2009 wird der dann im Ganzen decodiert (TEncoding) und danach als Ganzes an SetTextStr aka .Text übergeben ... das Einlesen ist also nur ein Bruchteil dessen, was da passiert.
Also bringt es nicht viel, da was NUR am Stream machen zu wollen. Hier hätte man erstmal eine Variante für BIS Delphi 2007, wo allergings erstmal nur die Stringaufteilung beachtet wird. [add] OK, hab noch schnell LoadFromStream reingemacht, wo und nur wird mit -1 der Start des Einlesens der Daten aus dem Stream, bzw. aus der Datei angezeigt und bei 0 würde mit dem Zerlegen des Textes begonnen. Wenn das jemand noch für D2009/D2010 haben will, so möge er sich melden. (dort wurde ja so Einiges erweitert) [/add]
Delphi-Quellcode:
Will man auch noch das Laden haben, dann müßte man noch LoadFromStream überschreiben, dann hätte man schonmal den Begin des Ladens und die Zeit für's Decodieren, und will man auch noch das Laden selber, dann müßte man wirklich noch eine Streambehandlung implementieren.
// If Progress = -1 then, it will start reading the file/stream data.
Type TProgressyEvent = Procedure(Sender: TObject; Progress: Byte) of Object; TProgressStrings = Class(TStringList) Private FProgress: TProgressyEvent; Protected Procedure SetTextStr(Const Value: String); Override; Property OnProgress: TNotifyEvent Read FProgress Write FProgress; Public Procedure LoadFromStream(Stream: TStream); Override; End; Procedure TProgressStrings.SetTextStr(Const Value: String); Var P, Start: PChar; S: String; Begin BeginUpdate; Try If Assigned(FProgress) Then FProgress(Self, 0); Clear; P := Pointer(Value); If P <> nil Then While P^ <> #0 do Begin Start := P; While not (P^ in [#0, #10, #13]) do Inc(P); SetString(S, Start, P - Start); Add(S); If P^ = #13 Then Inc(P); If P^ = #10 Then Inc(P); If Assigned(FProgress) Then FProgress(Self, Length(Value) * 100 div (Integer(P) - Integer(Value))); End; If Assigned(FProgress) Then FProgress(Self, 100); Finally EndUpdate; End; End; Procedure TStrings.LoadFromStream(Stream: TStream); Var Size: Integer; S: String; Begin BeginUpdate; Try Size := Stream.Size - Stream.Position; If Assigned(FProgress) Then FProgress(Self, -1); SetString(S, nil, Size); Stream.Read(Pointer(S)^, Size); SetTextStr(S); Finally EndUpdate; End; End; |
Re: Progressbar um Loadfromfile fortschritt anzuzeigen.
Zitat:
Egal, ich habe vorhin gemerkt, das das eigentliche Laden schnell genug geht, (liege da bei unter 5 sekunden) es sind zwei dahintergeschaltete verarbeitende proceduren,welche die Zeit in Anspruch nehmen, das werde ich also anders lösen und meine progressbar abhängig von diesen Durchläufen steuern, das bekomme ich hin. |
Re: Progressbar um Loadfromfile fortschritt anzuzeigen.
Zitat:
ich habe versucht es in eine anwendung reinzupasten dabei musste ich aber die Zeile
Delphi-Quellcode:
in
Procedure TStrings.LoadFromStream(Stream: TStream);
Delphi-Quellcode:
ändern weil mein delphi meinte Unit2 enthielte kein element mit namen Loadfromstream...
Procedure TProgressStrings.LoadFromStream(Stream: TStream);
jetzt sind die fehler weg aber wenn ich compilieren will kommt für die Zeile:
Delphi-Quellcode:
die Fehlermeldung:
Property OnProgress: TNotifyEvent Read FProgress Write FProgress;
Zitat:
testweise habe ich zur zeit folgende ladeprocedur in einen butten geklatscht:
Delphi-Quellcode:
und eine progressbar in die form gezogen... wie vertüdel ich jetzt das alles richtig?
procedure TForm2.Button1Click(Sender: TObject);
var laden:Topendialog; var start,dauer:Cardinal; var sl:tstringlist; begin Laden:=Topendialog.create(self); sl:=TStringlist.Create; if Laden.execute then begin start := GetTickCount(); sl.LoadFromFile(laden.FileName); end; dauer := GetTickCount() - start; panel1.caption:='Laden hat '+(floattostr(dauer/1000))+' Sekunden gedauert'; end; |
Re: Progressbar um Loadfromfile fortschritt anzuzeigen.
Ups ... ja, da ist ein kleiner Fehler drinnen.
TStrings.LoadFromStream muß natürlich TProgressStrings.LoadFromStream heißen und im OnProgress ist auch ein Copy&Paste-Fehler :oops: Zitat:
Das hier dürfte jetzt wohl mindestens ab Delphi 7 laufen (hoff ich mal)
Delphi-Quellcode:
Du mußt jetzt im Prinzip nur noch statt TStringList die TProgressStringList zum Einlesen verwenden,
// If Progress is -3, then starting to read the file or stream.
// If Progress is -2, then starting to decode. (only in Delphi 2009 and successors) // If Progress is -1, then started to empty the old list. // If Progress is 0, then starting the Add. // If Progress is between 0 and 10000, then the text will be added to the list // and "Progress" is the progress in hundredths of a percent. // If Progress is 10000, then the read is completed. Type TProgressEvent = Procedure(Sender: TObject; Progress: Integer) of Object; TProgressStringList = Class(TStringList) Private FProgress: TProgressyEvent; Protected Procedure SetTextStr(Const Value: String); Override; Property OnProgress: TProgressEvent Read FProgress Write FProgress; Public {$IF Declared(TEncoding)} Procedure LoadFromStream(Stream: TStream; Encoding: TEncoding); Override; {$ELSE} Procedure LoadFromStream(Stream: TStream); Override; {$IFEND} End; Procedure TProgressStringList.SetTextStr(Const Value: String); {$IF Declared(TEncoding)} Var P, Start, LB: PChar; S: String; LineBreakLen: Integer; Begin BeginUpdate; Try If Assigned(FProgress) Then FProgress(Self, -1); Clear; If Assigned(FProgress) Then FProgress(Self, 0); P := Pointer(Value); If P <> nil Then If CompareStr(LineBreak, sLineBreak) = 0 Then Begin // This is a lot faster than using StrPos/AnsiStrPos when // LineBreak is the default (#13#10) While P^ <> #0 do Begin Start := P; While not (P^ in [#0, #10, #13]) do Inc(P); SetString(S, Start, P - Start); Add(S); If P^ = #13 Then Inc(P); If P^ = #10 Then Inc(P); If Assigned(FProgress) Then FProgress(Self, Int64(Length(Value)) * 9999 div ((Integer(P) - Integer(Value)) div SizeOf(Char))); End; End Else Begin LineBreakLen := Length(LineBreak); While P^ <> #0 do Begin Start := P; LB := AnsiStrPos(P, PChar(LineBreak)); While (P^ <> #0) and (P <> LB) do Inc(P); SetString(S, Start, P - Start); Add(S); If P = LB Then Inc(P, LineBreakLen); If Assigned(FProgress) Then FProgress(Self, Int64(Length(Value)) * 9999 div ((Integer(P) - Integer(Value)) div SizeOf(Char))); End; End; If Assigned(FProgress) Then FProgress(Self, 10000); Finally EndUpdate; End; End; {$ELSE} Var P, Start: PChar; S: String; Begin BeginUpdate; Try If Assigned(FProgress) Then FProgress(Self, -1); Clear; If Assigned(FProgress) Then FProgress(Self, 0); P := Pointer(Value); If P <> nil Then While P^ <> #0 do Begin Start := P; While not (P^ in [#0, #10, #13]) do Inc(P); SetString(S, Start, P - Start); Add(S); If P^ = #13 Then Inc(P); If P^ = #10 Then Inc(P); If Assigned(FProgress) Then FProgress(Self, Int64(Length(Value)) * 9999 div ((Integer(P) - Integer(Value)) div SizeOf(Char))); End; If Assigned(FProgress) Then FProgress(Self, 10000); Finally EndUpdate; End; End; {$IFEND} {$IF Declared(TEncoding)} Procedure TProgressStringList.LoadFromStream(Stream: TStream; Encoding: TEncoding); Var Size: Integer; Buffer: TBytes; S: String; Begin BeginUpdate; Try Size := Stream.Size - Stream.Position; If Assigned(FProgress) Then FProgress(Self, -3); SetLength(Buffer, Size); Stream.Read(Buffer[0], Size); If Assigned(FProgress) Then FProgress(Self, -2); Size := TEncoding.GetBufferEncoding(Buffer, Encoding); S := Encoding.GetString(Buffer, Size, Length(Buffer) - Size); SetTextStr(S); Finally EndUpdate; End; End; {$ELSE} Procedure TProgressStringList.LoadFromStream(Stream: TStream); Var Size: Integer; S: String; Begin BeginUpdate; Try Size := Stream.Size - Stream.Position; If Assigned(FProgress) Then FProgress(Self, -3; SetString(S, nil, Size); Stream.Read(Pointer(S)^, Size); SetTextStr(S); Finally EndUpdate; End; End; {$IFEND} dem OnProgress eine Ereignisprozedur verpassen und darin dann deine Progressbar anzeigen. 'nen einfaches Beispiel wäre z.B.:
Delphi-Quellcode:
Bei dem dekodieren kann man nicht viel machen, da man dort nicht reinkommt,
Procedure TForm1.MyProgressEvent(Sender: TObject; Progress: Integer);
Begin Case Progress of -3: Label1.Caption := 'lese Datei...'; -2: Label1.Caption := 'dekodiere...'; -1: Label1.Caption := 'leere alte Liste'; 10000: Label1.Caption := 'fertig'; Else Begin Label1.Caption := 'Add'; ProgressBar1.Position := Progress; End; End; End; aber wenn das "lese Datei..." noch zu lange dauert, dann könnte man da eben noch den Stream mit in den Fortschritt aufnehmen. [add] also in etwa so
Delphi-Quellcode:
procedure TForm2.ProgressEvent(Sender: TObject; Progress: Integer);
begin case Progress of -3: Panel1.Caption := 'lese Datei ein ...'; -2: Panel1.Caption := 'dekodiere den Text ...'; -1: Panel1.Caption := 'leere alte Liste ...'; 10000: Panel1.Caption := 'fertig'; else begin Panel1.Caption := 'befülle die Liste'; ProgressBar1.Position := Progress div 100; // .Min=0 und .Max=100 end; end; Application.ProcessMessages; end; procedure TForm2.Button1Click(Sender: TObject); var laden: TOpenDialog; start, dauer: Cardinal; sl: TStringList; // kann auch TProgressStringList sein begin laden := TOpenDialog.Create(self); try if laden.Execute then begin sl := TProgressStringList.Create; try start := GetTickCount(); sl.OnProgress := ProgressEvent; sl.LoadFromFile(laden.FileName); dauer := GetTickCount() - start; Panel1.Caption := 'Laden hat ' + (floattostr(dauer/1000)) + ' Sekunden gedauert'; //... finally sl.Free; end; end; finally laden.Free; end; end; |
Re: Progressbar um Loadfromfile fortschritt anzuzeigen.
Zitat:
auffallen tut mir das meine anzeige... laden hat... gedauert von ca. 4 auf 22 sekunden springt, meine anwendung aber offenbar früher wieder befehle entgegennimmt... Zitat:
Delphi-Quellcode:
geändert, denke das war gemeint, oder?
Application.ProcessMessages;
|
Re: Progressbar um Loadfromfile fortschritt anzuzeigen.
Application.ProcessMessages verarbeitet die Windowsbotschaften, also es sorgt für das "früher wieder befehle entgegennimmt" und ist auch dafür gedacht, daß sich das Label und die ProgressBar neu zeichnen.
Und daß es "etwas" länger dauert ist auch klar, immerhin werden jetzt nach jeder Zeile der Datei ein Label, die ProgressBar und alle möglichen Windwsbotschaften verarbeitet ... welches ja zusätzliche Zeit benötigt. Beschleunigen könnte man es nur dadurch, daß z.B. die Ereignisprozedur nicht nach jeder, sondern nur alle 100 Zeilen aufgerufen würde. Und wenn man das Label und die ProgressBar manuel zum Neuzeichnen bringt, dann könnte man damit Application.ProcessMessages ersetzen. |
Re: Progressbar um Loadfromfile fortschritt anzuzeigen.
Zitat:
da kommt je nachdem was ich lade alles mögliche aber fast nie zahlen zwischen 0 und 10000. bei kleinen datenmengen (paar kilobyte) fängt es mit großen zahlen an und konvergiert langsamer werdend gegen 9999. bei größeren datensätzen (>10Mb) föngt es mit riesigen negativen zahlen an und konvergiert ebenfalls langsamer werdend gegen 0. hier mal die ersten paar werte von progress bei nem 11Mb datensatz: 0 -106374335 -23184150 -19237911 -11910370 usw usw später gibt progress dan x mal den selben wert zurück -175 -175 -175 -175 -174 -174 usw usw das kann ich also nicht auswerten für die progressbar... |
Alle Zeitangaben in WEZ +1. Es ist jetzt 03:23 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz