19. januar 2004 - 19:26Der er
13 kommentarer og 1 løsning
Listbox.Items.LoadfromFile
Jeg har et program hvor jeg skal indlæse en temmelig stor mængde data i en listbox (ca. 25.000 linier). Jeg benytter LoadfromFile, men vil gerne ha' en progressbar på, da indlæsningen godt kan ta' op til 2 minutter. Er det muligt ved fx. LoadFromStream at bestemme den samlede mængde data der skal indlæses og samtidig styre hvor langt indlæsningen er kommet, eller skal jeg i gang med noget BlockRead? El lille eksempel ville være velkommen.
Jeg har arbejdet lidt videre med problemet. Jeg kan bruge en Filestream, hvilket er hurtigt, men har så et problem at få bufferen (array of byte) konverteret til string og samtidig forsvinder mine linieskift. En anden måde kunne være at finde filen størrelse, læse den første linie med readln, finde længden af 1 linie og ud fra filstørrelsen finde antal linie i filen. Herefter kan jeg indlæse 1 linie ad gangen. Tager ca 15% længere tid end at bruge LoadFromFile, men så har jeg en progressbar. Tager stadig imod gode råd :-)
jeg forstod selv engang at det jo er selve parsingen af tekstenfilen ind i listboxen der tager tid, ikke selve det at "læse" filen. så vidt jeg husker kræver det at du kontruere en ny listbox, som du selv har kontrol over ved LoadFromFile så du kan vise evt. progress i en progressbar.
Jeg har lavet en noget smartere løsning (den er 200 linier men læser 25000 (1,5MB) navne på under 1 sekund...) Jeg har en form med tre TEdits (eName,eAdr,eAge), Add-knap (dele hele i et panel) - og så lige en TListBox. Dertil har jeg lavet to klasser, et liste og et data-objekt.
Humlen i det hele er, at man sætter listboksens style til lbVirtual og bruger de to events OnData og OnDataObject.
procedure TfrmMain.FormCreate(Sender: TObject); //var // i : integer; begin fMyList := TMyList.Create(lbData); if fileexists('c:\BigFile.txt') then fMyList.LoadFromFile('c:\BigFile.txt');
// Uncomment to create test data // for i := 0 to 24999 do // fMyList.Add(TMyData.Create('Eksperten er Gud','Ekspertvejen 12',2)); end;
procedure TfrmMain.FormDestroy(Sender: TObject); begin fMyList.SaveToFile('c:\BigFile.txt'); fMyList.Free; end;
procedure TfrmMain.lbDataData(Control: TWinControl; Index: Integer; var Data: String); begin Data := fMyList[Index].Name; end;
procedure TfrmMain.btnAddClick(Sender: TObject); begin fMyList.Add(TMyData.Create(eName.Text,eAdr.Text,StrToInt(eAge.Text))); end;
procedure TfrmMain.lbDataDataObject(Control: TWinControl; Index: Integer; var DataObject: TObject); begin DataObject := FMyList[Index]; end;
procedure TfrmMain.lbDataClick(Sender: TObject); var MyData : TMyData; begin with (Sender as TListBox) do MyData := TMyData(Items.Objects[ItemIndex]);
function TMyList.Add(aMyData: TMyData): integer; begin result := inherited Add(TClass(aMyData)); fListBox.Count := Count; end;
constructor TMyList.Create(aListBox: TCustomListBox); begin inherited Create; fListBox := aListBox; end;
procedure TMyList.Delete(aIndex: integer); begin inherited Delete(aIndex); fListBox.Count := Count; end;
function TMyList.GetMyData(aIndex: integer): TMyData; begin result := TMyData(inherited Items[aIndex]); end;
procedure TMyList.LoadFromFile(const aFileName: string); var fs : TFileStream; begin fs := TFileStream.Create(aFileName,fmOpenRead); try while fs.Position < fs.Size do Add(TMyData.Create(fs)); finally fs.Free; fListBox.Count := Count; end; // try end;
procedure TMyList.SaveToFile(const aFileName: string); var i : integer; fs : TFileStream; begin fs := TFileStream.Create(aFileName,fmCreate); try for i := 0 to Count - 1 do Items[i].Write(fs); finally fs.Free; end; // try end;
{ TMyData }
constructor TMyData.Create(aStream: TStream);
function GetString(aStream: TStream) : string; var p : pchar; size : integer; begin aStream.Read(size,sizeof(integer)); getmem(p,size+1); try fillchar(p^,size+1,0); aStream.Read(p^,size); result := string(p); finally freemem(p); end; // try end;
Du skal ikke blive afskrækket af at det fylder en del, det meste er "fyldkode" som jeg lavede for at kunne vise dig de virtuelle lister.
Lad høre hvis der er spørgsmål...
N.B. Der er en del komponenter der kan bruge de virtuelle lister, eksempelvis TListView.
N.B.2. I øvrigt er det et spørgsmål om det er god GUI at have 25 linier i en listbox - men det er din beslutning.
N.B.3. Bemærk sidst, at jeg fører listboksen over i TMyList's constructor. Det er nødvendigt fordi jeg skal administrere dens "virtuelle" forestilling om hvor mange enheder der er i listen).
Det er et impornerende arbejde du har lavet, men ... Når jeg prøver dit program på min tekstfil, går der meeeget lang tid hvorefter programmet stopper med en Out of memory fejl. Jeg kan heller ikke se, at du har gjort noget ved mit oprindelige problem, nemlig en progress-bar under indlæsningen.
Jeg indlæser en fil i eget format, så det er klart at noget uventet vil forekomme når man høvler din fil igennem. Den opbektorienterede idé i det er, at kun TMyData kender opbygningen af filen. I mit objekt er opbygningen "size name size adr size age" pr. record (klodset, men det virker)
Jeg ved godt at dit problem mht. progressbar'en ikke er løst, men mit eksempel indlæser (på min 800Mhz bærbare) 75000 records på ca. 1 sekund og så er der ikke brug for en progressbar. Prøv at fjerne udkommenteringen i OnCreate. Dette vil generere 25000 records hver gang du starter programmet (dette efter indlæsning af eksisterende fil, så det kommer 25k til hver gang). Disse gemmes når programmet lukkes.
Hvis det er en fil du selv administrerer, så bliver det meget meget lettere, for så skriver du antallet af records først. Så er der ingen problemer med at lave en progressbar og få den til at tælle op korrekt.
procedure TMyList.LoadFromFile(const aFileName : string; aProgrssBar : TProgrssBar); var fs : TFileStream; i, Count : integer; Skip : integer; begin fs := TFileStream.Create(aFileName,fmOpenRead); try aStream.Read(Count,sizeof(integer));
aProgressBar.Min := 0; aProgressBar.Max := Count;
Clear; // Clear and free any existing items in the list
for i := 0 to Count - 1 do begin Add(TMyData.Create(fs)); ProgressBar.Position := i;
Disse to metoder dur, men medmindre du sender en Message til formen (så den vises), så ser du kun noget når du tilretter programmet, så der er knapper til indlæsning og skrivning.
procedure TMyList.LoadFromFile(const aFileName: string; aProgressBar : TProgressBar); var fs : TFileStream; i, Skip, RecCount : integer; begin fs := TFileStream.Create(aFileName,fmOpenRead); try fs.Read(RecCount,sizeof(integer));
if assigned(aProgressBar) then begin aProgressBar.Position := 0; aProgressBar.Min := 0; aProgressBar.Max := RecCount; end; // if
Skip := RecCount div 100;
for i := 0 to RecCount - 1 do begin if (i mod Skip) = 0 then if assigned(aProgressBar) then begin aProgressBar.Position := i; Application.ProcessMessages; end; // if
procedure TMyList.SaveToFile(const aFileName: string; aProgressBar : TProgressBar); var i : integer; Skip : integer; fs : TFileStream; begin fs := TFileStream.Create(aFileName,fmCreate); try fs.Write(Count,sizeof(integer));
if assigned(aProgressBar) then begin aProgressBar.Position := 0; aProgressBar.Min := 0; aProgressBar.Max := Count; end; // if
Skip := Count div 100;
for i := 0 to Count - 1 do begin if (i mod Skip) = 0 then if assigned(aProgressBar) then begin aProgressBar.Position := i; Application.ProcessMessages; end; // if
Items[i].Write(fs); end; // for finally fs.Free; end; // try end;
procedure TMyList.LoadFromFile(const aFileName: string; aProgressBar : TProgressBar); var fs : TFileStream; i, Skip, RecCount : integer; begin fs := TFileStream.Create(aFileName,fmOpenRead); fListBox.Items.BeginUpdate; // <- Dette hjælper også meget! try fs.Read(RecCount,sizeof(integer));
if assigned(aProgressBar) then begin aProgressBar.Position := 0; aProgressBar.Min := 0; aProgressBar.Max := RecCount; end; // if
Skip := RecCount div 100;
for i := 0 to RecCount - 1 do begin if assigned(aProgressBar) then if (i mod Skip) = 0 then begin aProgressBar.Position := i; Application.ProcessMessages; // Update GUI end; // if
Undskyld jeg spammed dig sådan - har hygget mig ved at optimere mit eksempel. Hvordan udveksler man egentlig eMails uden at skrive det her? Kan man mon det i Eksperten?
>>Undskyld jeg spammed dig sådan Det er helt fint med mig. Normalt ville jeg gøre programmet færdigt på én dag, men jeg har rasende travlt for øjeblikket, så jeg har ikke en gang haft tid til at nærlæse din kode. Pointene har du fortjent for lang tid siden.
Det skal da heller ikke afholde mig fra at fortsætte (Odenseaner til Odenseaner):
Idé 1:
Jeg læser en binærfil, mens du læser en tekstfil. Det kræver en lidt anden tilgang. Igen er den tunge del at populere listboksen, ikke at læse selve filen. Derfor vil jeg indlæse filen i en TMemoryStream (den har også en LoadFromFile), tælle <CR/LF> for at få antallet af records. Derefter vil jeg parse data og indsætte linierne i listboksen.
... men man kan jo gå over åen efter vand...
Idé 2:
Først finder du størrelsen af tekstfilen og det tæller du op efter (og det er vel egentlig den løsning som du forespurgte fra starten - vil dog stadig anbefale at bruge det sammen med en virtuel ListBox).
const TheFile = 'c:\BigMF.txt'; var Step, NextStep : integer; fp : TextFile; st : string; begin if not FileExists(TheFile) then exit;
assignfile(fp,TheFile); reset(fp); lbData.Items.BeginUpdate; Screen.Cursor := crHourGlass; try lbData.Clear; while not eof(fp) do begin readln(fp,st); lbData.Items.Add(st); pbStatus.StepBy(length(st)+2); // remember to count cr/lf if NextStep <= pbStatus.Position then begin Application.ProgressMessage; inc(NextStep,Step); end; // if end; // while finally lbData.Items.EndUpdate; closefile(fp); Screen.Cursor := crDefault; end; // try end;
P.S. Har du i øvrigt prøvet at sætte LoadFromFile mellem BeginUpdate/EndUpdate?
Der sker vist ikke mere i sagen, så tak for hjælpen.
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.