02. juni 2004 - 13:33Der er
12 kommentarer og 1 løsning
gemme array of record vha. TFileStream
Hejsa
jeg har et "luksus" problem.. men det irriterer mig kolosalt at jeg ikke er i stand til at gøre min procedure mere smart.. så derfor poster jeg her mit problem.
jeg bruger TFileStream til at gemme et dynamisk array af record i en flad fil.
Anyway.. min procedure ser sådan her ud:
procedure TForm1.Button8Click(Sender: TObject); var x: integer; gridarray: array of Tdeltur; i: Integer; Writer: TWriter; FileStream: TFileStream; begin for x := 0 to 10 do begin setlength(gridarray, length(gridarray) + 1); gridarray[x].indhold := inttostr(x); gridarray[x].navn := 'TTextualColumn'; gridarray[x].art := 'TTextualColumn'; end;
FileStream := TFileStream.Create('hejsa.txt', fmCreate); Writer := TWriter.Create(FileStream, $FF); Writer.WriteListBegin; for i := 0 to Length(gridarray) - 1 do begin Writer.WriteString(gridarray[i].indhold); Writer.WriteString(gridarray[i].navn); Writer.WriteString(gridarray[i].art); Writer.Write(); end;
Disse ting havde jeg liggende i en variant af en TList hvor der var en:
SaveToStream(const aFileName : string);
der udførte følgende:
procedure TMyList.SaveToStream(const aFileName : string); var i : integer; fs : TFileStream; begin fs := TFileStream.Create(aFileName,fmCreate); try for i := 0 to Count - 1 do TDataClass(Items[i]).SaveToStream(fs); finally fs.Free; end; end;
... og jeg beklager - det er ikke så kort som det du roder med, men jeg foretrækker klasser frem for dynamiske lister (og det er nok både uretfærdigt og konservativt). Synes det er genialt at kunne tilknytte handlinger (metoder) til de data man roder med, her SaveToStream. En anden ting jeg fravalgte var at gemme i en tekstfil. Jeg valgte også at gå udenom TWriter (havde ikke set den før) idet jeg synes det er lettere at gemme på min måde. Det løser altså ikke dit problem, men kan måske give inspiration.
jamen dog.. nej jeg synes du har ret - det er lettere at gå over floden efter vand - specielt når man programmerer... men jeg er ingen ørn og slet ikke programmør ;)
Så jeg må klare mig med hvad jeg kender og kan.
Jeg kan godt se at det er smart det som du forslår - jeg tror bare ikke at jeg kan styre det "skib" sikkert i havn.
Men jeg er lidt nysgerrig - fordi hvordan ser din variant af Tlist ud?..
Jeg har iøvrigt fundet ud af i mellemtiden at det slet ikke kan lade sige gøre at gemme et dynamisk array til en fil ... hvis man ønsker at gøre det som jeg lagde op til.
Det skyldes åbenbart at delphi allokerer ressourcer til arrayets elementer og hvis delphi ikke ved hvor store elementerne er, så kan delphi heller ikke håndtere at skulle give dem en ens artet struktur ved en "gemning til fil" procedure.
Løsningen på problemet er at man bruger statistiske arrays og definerer sine variable som f.eks. string[100].. altså strings med fast størrelse.. og det var jo ikke dette jeg var ude efter.!
Når jeg indsætter noget af din procedure i mit lille program.. får jeg "invalid typecast".
procedure TForm1.Button12Click(Sender: TObject); var gridarray: Tgridarray; i: Integer; FileStream: TFileStream; begin for i := 0 to 10 do begin // setlength(gridarray, length(gridarray) + 1); gridarray[i].indhold := inttostr(x); gridarray[i].navn := 'TTextualColumn'; gridarray[i].art := 'TTextualColumn'; end;
FileStream := TFileStream.Create('hejsa.txt', fmCreate); for i := 0 to Length(gridarray) - 1 do FileStream.WriteBuffer(pchar(gridarray[i].indhold)^, length(gridarray[i].indhold)); FileStream.WriteBuffer(pchar(gridarray[i].art)^, length(gridarray[i].art)); FileStream.WriteBuffer(pchar(gridarray[i].navn)^, length(gridarray[i].navn)); end; FreeAndNil(FileStream); end.
DataSize skal jeg bruge når jeg indlæser data igen. Her får du det fulde eksempel. I bunden har jeg demonstreret hvordan man genererer og indlæser data fra listen.
uses SysUtils, ContNrs, Classes;
type TDataArts = (daTextualColumn); // One for now - more to come
TDataClass = class private fIndhold : string; fArt : TDataArts; fNavn : string; public // Man kan oprette på to måder: Ved nyoprettelse bruges denne constructor Create(const aArt : TDataArts; const aNavn, aIndhold : string); overload; // Ved indlæsning bruger man denne constructor Create(aStream : TStream); overload; property Art : TDataArts read fArt; // read only property Navn : string read fNavn; // ditto property Indhold : string read fIndhold write fIndhold; // Indhold kan ændres procedure SaveToStream(aStream : TStream); end;
TDataList = class(TObjectList) private function GetDataClass(aIndex: integer): TDataClass; public constructor Create; reintroduce;
constructor TDataClass.Create(aStream: TStream); var DataSize : integer; Buffer, p : pchar; begin { Data ligger på følgende måde: DataSize (4 bytes), Navn med et #0 i enden, Indhold med et #0 i enden og Art (der vist fylder 1 byte }
aStream.Read(DataSize,sizeof(integer)); GetMem(Buffer,DataSize); // Her bruger jeg DataSize try aStream.ReadBuffer(Buffer^,DataSize); p := Buffer; // Buffer rummer data, p er en pegepind der flytter rundt deri
fNavn := p; // Pchars og strenge er kompatible p := StrEnd(p) + 1; // Ryk til næste streng i listen (fIndhold)
fIndhold := p; p := StrEnd(p) + 1; // Ryk til næste element i listen (fArt)
fArt := TDataArts(p^); // Tjek at den indlæses korrekt - er ikke helt sikker finally FreeMem(Buffer,DataSize); end; // try end;
constructor TDataList.Create; begin // True sikrer at når listen frigives, så frigives elementerne også inherited Create(true); end;
function TDataList.GetDataClass(aIndex: integer): TDataClass; begin result := TDataClass(inherited Items[aIndex]); end;
procedure TDataList.ReadFromFile(aFileName: string); var fs : TFileStream; begin fs := TFileStream.Create(aFileName,fmOpenRead); try ReadFromStream(fs); finally fs.Free; end; end;
procedure TDataList.ReadFromStream(aStream: TStream); begin while aStream.Position <> aStream.Size do Add(TDataClass.Create(aStream)); end;
procedure TDataList.SaveToFile(aFileName: string); var fs : TFileStream; begin fs := TFileStream.Create(aFileName,fmCreate); try SaveToStream(fs); finally fs.Free; end; end;
procedure TDataList.SaveToStream(aStream: TStream); var i : integer; begin for i := 0 to Count - 1 do Items[i].SaveToStream(aStream); end;
------------------------- o -------------------------
I min form har jeg to knapper:
procedure TfrmMain.Button1Click(Sender: TObject); var i : integer; DataList : TDataList; begin DataList := TDataList.Create; try for i := 0 to 99 do DataList.Add(TDataClass.Create(daTextualColumn,'Hans','Hedtoft')); DataList.SaveToFile('c:\data.dat'); finally DataList.Free; end; end;
procedure TfrmMain.Button2Click(Sender: TObject); var i : integer; DataList : TDataList; begin DataList := TDataList.Create; try DataList.ReadFromFile('c:\data.dat'); for i := 0 to DataList.Count - 1 do ListBox1.Items.Add(DataList[i].Navn); finally DataList.Free; end; end;
iøvrig vil jeg gerne undskylde at jeg ikke har vendt tilbage før nu.. men jeg har været alvorlig syg så computere og mine programmeringshobby har haft meget lav priotet.
... men det virker nu - dejligt :)
...men jeg har lige et supplerende spørgsmål til dig hrc.. når nu jeg er gået over til din måde at tænke på.. så kommer jeg til at tænke på super og sup klasser.
Det er noget med at man kan bruge samme funktion til at gemme forskellige lister - f.eks. liste_kunder og liste_ordre osv. Men ved at have "gemmeproceduren" i superklassen og de enkelte oplysninger/egenskaber for en liste i en supklasse.
Er jeg helt galt på den?.. eller er det ikke måden at gøre det på?.. i så fald hvordan pokker fungerer det så - hvis vi tager udgangspunkt i dit eget eksempel?.
Jeg skal ned og have kulhydrater, proteiner og ikke for meget fedt indenbors, så det bliver lidt kort...
TDataClass kunne nedarve fra en TBaseDataClass á lá:
TBaseDataClass = class private fArt : TDataArts; fNavn : string; protected property Art : TDataArts read fArt; // read only property Navn : string read fNavn; // ditto public // Man kan oprette på to måder: Ved nyoprettelse bruges denne constructor Create(const aArt : TDataArts; const aNavn : string); overload; // Ved indlæsning bruger man denne constructor Create(aStream : TStream); virtual; overload; procedure SaveToStream(aStream : TStream); virtual; end;
Jeg har udskildt fællesnævnerne (Art og Navn) - en optimering som man kan diskutere hører hjemme i base-klassen eller måske i en klasse midt imellem (TBaseArtKlasse?), men man ender sgu tit med en hel række af klasser - og der løber jeg lidt sur i OOP'en.
Den ene constructor bliver virtual så den må overrides af en nedarvende klasse (men kaldes jo via "inherited"). Det samme gælder SaveToStream;
De to properties bliver "protected" det betyder at de nedarvende klasser bliver nødt til at skrive "Property Art" i deres public-sektion for at de bliver synlige - kan sige at det er ret optionelt.
I TDataListen skal TDataClass erstattes med TBaseDataClass, men det er vist også alt der.
Håber i øvrigt at du er kommet dig godt. Mht. gemmeproceduren, som du nævner, så er den ikke 100% placeret i superklassen sådan som jeg har lavet de her - nærmere splittet op. Jeg tror ikke det kan være anderledes for en superklasse kender ikke sine sub-klasser.
Når du fra TDataListen indlæser må listen sørge for at oprette de rigtige data-klasser, mens det at gemme:
procedure TDataList.SaveToStream(aStream: TStream); var i : integer; begin for i := 0 to Count - 1 do Items[i].SaveToStream(aStream); end;
er uændret idet den rigtige gemme-metode automatisk vil blive kaldt. Tumler lidt med om det kan laves smartere - måske dukker der noget op (men nu må jeg altså igang med at tjene penge igen).
I øvrigt vil Art endda være unødvendig hvis hver nedarvede klasse håndterer sin specifikke Art - så kunne man spørge på klassen if (theClass is TOpelClass) then, men det vil være usmart i forbindelse med at bruge "case": case theClass.Art of ...
Mange tak for hjælpen og den lille guide.. dejligt :)
Jeg vil prøve at grave dybere i emnet - så det kan jo være at vi støder på hinanden igen på eksperten ;)
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.