[Delphi] Opslaan en inlezen van uit eigen bestandsformaat

Pagina: 1
Acties:
  • 232 views sinds 30-01-2008
  • Reageer

  • Oscar Mopperkont
  • Registratie: Februari 2001
  • Laatst online: 03-08-2024
Ik ben bezig met een programma waarbij er een variabel aantal Richedits (een matrix) op een form staan. Het programma voegt deze samen en creeert 1 rtf bestand ervan. (ff in het heel kort dus, prog lijkt zo nergens op te slaan, maar goed)

Nu wil ik echter de mogelijkheid hebben om tussentijds op te slaan. Het programma moet de volgende dingen opslaan:
- Het aantal Richedits (het is een matrix dus het aantal in de x en y richting)
- De inhoud/tekst van de Richedits
- De captions van de bijbehorende labels
- en nog wat vergelijkbare data

Nu heb ik daar nog geen ervaring mee en ik heb wat zoekwerk gedaan en dan kom ik allemaal termen tegen die mij (nog) weinig zeggen. Wat mij nog het eenvoudigste leek was gebruik maken van een TFilestream en daarmee de data opslaan en inlezen (dan kan ik in ieder geval de opmaak van de Richedits behouden). Dus ik heb nu een procedure gemaakt die de boel wegschrijft in en bestand.
Delphi:
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
procedure TfrFormulier.OpslaanClick(Sender: TObject);
var
  i,j: integer;
  F:  TextFile;
  oStream:  TFileStream;
begin
  // Initialiseer SaveDialog
  SaveDialog1.DefaultExt:='gra';
  SaveDialog1.Filter:='(gra, *.gra)|*.gra;';

  if (SaveDialog1.Execute) then
  begin
    oStream := TFileStream.Create(SaveDialog1.FileName,fmCreate);

    for i := 0 downto clLabels1.Count-1 do
    begin
      oStream.WriteComponent(clLabels1[i]);
    end;

....

    for i :=0 to clComboBoxen.Count-1 do
    begin
      oStream.WriteComponent(clComboBoxen[i]);
    end;

    oStream.Free;
  end;
end;

Op deze manier slaat hij dus alle info op van alle componenten die ik heb opgeslagen, in ieder geval dat denk ik.

Ik heb dus al een beginnetje, maar ik zou niet weten hoe ik nu verder moet. Ik heb dan ook de volgende vragen.

1) Hoe kan ik het aantal richedits opslaan, dus die X en Y waarde in de filestream

2) Slaat hij op deze manier allen info op van de componenten? Ik kan namelijk wel in de file terugvinden dat hij de dingen die ik typ in een Richedit opslaat, maar niet dat hij de caption van een Label opslaat.

3) Hoe pak ik het inlezen weer aan zodat ik weer alles kan creeren zoals het was? Ik wil die X en Y waarde opslaan zodat ik bij het inlezen weet dat de volgende X*Y streams van een richedit zijn. (ik neem aan dat dat handig is)

Verwijderd

De volgende informatie is onder voorbehoud omdat ik niet zo heel erg bekend ben met het streaming mechanisme van Delphi components.

WriteComponent is voor zover ik weet bedoeld om componenten op te slaan. Let op dat er dus een verschil is met alleen de gegevens die in een component staan (in jouw geval, de tekst van een RichEdit). WriteComponent zal ook alle andere properties (waaronder de positie op het form, om maar iets te noemen) wegschrijven naar de stream. Als je daar rekening mee houdt is het denk ik de makkelijkste manier om een component[1] of meerdere componenten weg te schrijven naar een bestand. Mede omdat deze methode zelf weet waar een blok gegevens begint en eindigt.

Een alternatief is zelf de gegevens uit de TRichEdit lezen[2] en deze opslaan door middel van TStream.Write. Wat je dan bij het uitlezen echter nog moet weten is hoe groot het blok informatie was dat je ging wegschrijven. De gebruikelijke manier om dit te doen is door het blok te prefixen met een integer die aangeeft hoe groot het blok is. Iets als volgt:

Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
procedure WriteChunk(Stream : TStream; Data : string);
var
  Ln : Cardinal;
begin
  Ln := Length(Data);
  Stream.Write(Ln, SizeOf(Ln));
  Stream.Write(Data, Ln);
end;

procedure ReadChunk(Stream : TStream) : string;
var
  Ln : Cardinal;
begin
  Stream.Read(Ln, SizeOf(Ln));
  SetLength(Result, Ln);
  Stream.Read(Result, Ln);
end;

{ Demo code -- kopiëert tekst van RichEdit1 naar RichEdit2 op een hele inefficiënte manier ;) }
MyStream := TMemoryStream.Create;
WriteChunk(MyStream, RichEdit1.Lines.Text);
MyStream.Position := 0;
RichEdit2.Lines.Text := ReadChunk(MyStream);


Twee caveats: deze code bevat geen error checking (je hoort bijvoorbeeld te controleren of het schrijven wel goed is gegaan, etc.), en hij faalt als je bestanden gaat porten tussen platformen met verschillende endianness of integergrootte, om de simpele reden dat de integer dan niet goed overkomt.

Het probleem van de matrixgrootte kan op dezelfde manier aangepakt worden. Je schrijft de lengte en breedte van de matrix als eerste naar de file[3]. Bij het uitlezen lees je eerst deze getallen in variabelen X en Y, en daarna lees je nog X*Y `data chunks' uit. Pseudocode:

Delphi:
1
2
3
4
5
6
7
8
9
{ Schrijven }
WriteInteger(X);
WriteInteger(Y);
for i := X * Y do WriteField(EditField[i]);

{ Lezen }
X := ReadInteger;
Y := ReadInteger;
for i := X * Y do EditField[i] := ReadField;



[1] Alleen voor componenten die afstammen van TPersistent, en TRichEdit doet dat inderdaad.
[2] Dit zou in principe moeten kunnen door de .Lines.Text property te benaderen. Ik weet echter niet zeker of je dan alle formatting informatie ook behoudt. Ik heb me eens laten vertellen dat RTF in principe gewoon een tekst-based formaat is (nooit onderzocht eigenlijk), dus het is waarschijnlijk dat die formatting info daar wel tussen zit, maar zeker weet ik het dus niet.
[3] Dat kan als integers, met alle problemen van dien, of als strings, wat weer vervelend proggen en suboptimaal is. Als iemand een betere manier heeft om getallen naar een stream te schrijven dan hoor ik het graag.

edit:
Een huisgenoot van mij maakt een goed punt: uit de door mij voorgestelde oplossing komen geen leesbare en kopiëerbare tekstfiles rollen. Vanwege de erin gepote integers moet het ding behandeld worden als binaire file.

Een andere (en portablere) oplossing is eventueel het ding als tekstfile te beschouwen, en de gegevensvelden (getallen, tekstvelden) te scheiden door middel van newlines.

Het probleem wat je dan nog over houdt is dat de gegevensvelden zelf newlines kunnen bevatten, waardoor je parsing algoritme de mist in kan gaan. Dit kun je afvangen door de tekst vantevoren te encoden, met bv. UU of Base64 encoding.

[ Voor 11% gewijzigd door Verwijderd op 15-12-2004 01:14 ]


  • Oscar Mopperkont
  • Registratie: Februari 2001
  • Laatst online: 03-08-2024
WriteInteger werkt toch alleen voor een ini file? Moet ik dus met ini files gaan werken?

Verwijderd

Je kunt het best even een eigen fileformaat verzinnen; dus een vaste indeling hoe spullen opgeslagen worden. Om daarin te lezen en te schrijven kun je gebruik maken van een filestream, maar ook van gewone i/o routines.

Een ini file is nog niet eens zo'n gek idee hiervoor, dan wordt er al een bestandsformaat afgedwongen en hoef je zelf niets te implementeren om een bepaald gegeven op te zoeken.

Je moet (nou ja, moeten) een filestream gewoon zien als een bestand waar je in schrijft. Als jij er eerst component 1 in zet, dan component 2 en dan bv. de waarde van die x en y, dan moet je later bij inlezen zelf uitzoeken waar die gegevens staan.

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 12:05

Tomatoman

Fulltime prutser

Een mooie oplossing zou het gebruik van een structured storage file zijn. Dat vergt echter wel wat werk en een goede kennis van COM en Delphi. Een structured storage file is een bestand dat vanbinnen is ingericht met een eigen soort file- en directorystructuur. Je kunt daarmee allerlei onderdelen netjes gestructureerd in het bestand opslaan. Daarmee voorkom je dat het een grote binaire ellende wordt die je moeizaam moet parsen.

Een goede grap mag vrienden kosten.


  • Oscar Mopperkont
  • Registratie: Februari 2001
  • Laatst online: 03-08-2024
tomatoman schreef op woensdag 15 december 2004 @ 23:23:
Een mooie oplossing zou het gebruik van een structured storage file zijn. Dat vergt echter wel wat werk en een goede kennis van COM en Delphi. Een structured storage file is een bestand dat vanbinnen is ingericht met een eigen soort file- en directorystructuur.
Lijkt me idd heel mooi, maar zoals je al zegt is een goede kennis van COM en Delohi vereist, ik weet niet eens wat COM is :X dus dat zegt denk ik genoeg.
Je kunt daarmee allerlei onderdelen netjes gestructureerd in het bestand opslaan. Daarmee voorkom je dat het een grote binaire ellende wordt die je moeizaam moet parsen.
Het is maar een klein programmatje, dus ik denk dat de binaire ellende op zich wel mee zal vallen. Zodra er meer en complexere dingen opgeslagen moeten worden, lijkt mij jou oplossing inderdaad een stuk beter :)

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 12:05

Tomatoman

Fulltime prutser

Oscar Mopperkont schreef op woensdag 15 december 2004 @ 23:36:
Het is maar een klein programmatje, dus ik denk dat de binaire ellende op zich wel mee zal vallen.
Als je er ook binaire gegevens in kwijt wilt kunnen, zou je het bestand kunnen beginnen met een header in plain text, waarin je zet op welke posities in het bestand bepaalde gegevens te vinden zijn. Zoiets bijvoorbeeld:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[Header]
RichEditCount=00000003

[Offsets]
RichEdit001=00000030
RichEdit002=00000FC4
RichEdit003=0000140A

[Lengths]
RichEdit001=00000F20 // lengte van de tekst onder [RichEdit001]
RichEdit002=00000EC4
RichEdit003=00000D0A

[RichEdit001] // de rechte haak [ staat op positie $00000030
{ Hier staat allerlei binaire rommel over rich edit 1 in de vorm van een }
{ stream. De lengte is $00000F20. }

[RichEdit001] // de rechte haak [ staat op positie $00000FC4
{ Hier staat allerlei binaire rommel over rich edit 2 in de vorm van een }
{ stream. De lengte is $00000EC4. }

[RichEdit003]
{ Nog meer binaire prut. }
Alle gegevens in plain text kun je nu bewerken alsof het een ini-file is. Delphi doet al het werk voor je.

Met behulp van de offsets kun je gemakkelijk via TFileStream een component streamen. De lengtes zijn niet eens strikt noodzakelijk als je al weet op welke offsets je welke informatie kunt terugvinden (TFileStream stopt vanzelf met lezen als de componenten zijn ingelezen), maar het kan ook geen kwaad. Het maakt het foutzoeken in ieder geval wat makkelijker.

Als je bij getallen voorloopnullen gebruikt, wordt het parsen van een bestand wat gemakkelijker. Bepaalde gegevens staan dan altijd op precies dezelfde plaats in het bestand.

[ Voor 23% gewijzigd door Tomatoman op 16-12-2004 00:02 ]

Een goede grap mag vrienden kosten.


Verwijderd

Oscar Mopperkont schreef op woensdag 15 december 2004 @ 22:59:
WriteInteger werkt toch alleen voor een ini file? Moet ik dus met ini files gaan werken?
Zoals ik er bij gezet had betrof het pseudocode. WriteInteger() geeft dus meer een soort van actie aan (die je zelf moet programmeren, maar hoe dat moet stond al aangegegeven), dan een functie die je kant en klaar aan kan roepen...

Verwijderd

Ik zou de zaak in een database plempen, bv in een BLOB of MEMO veld. Als je de assen niet apart definieert redt je het zelfs met 1 tabel. Een kale paradox of dBase tabel werkt ongeveer als een bestand. Maak van BDE je filedriver !

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 12:05

Tomatoman

Fulltime prutser

Verwijderd schreef op vrijdag 17 december 2004 @ 13:17:
Ik zou de zaak in een database plempen, bv in een BLOB of MEMO veld. Als je de assen niet apart definieert redt je het zelfs met 1 tabel. Een kale paradox of dBase tabel werkt ongeveer als een bestand. Maak van BDE je filedriver !
De BDE is obsolete en kun je beter niet meer gebruiken voor nieuwe applicaties. Een interessant alternatief is DBExpress.

Een goede grap mag vrienden kosten.


  • Oscar Mopperkont
  • Registratie: Februari 2001
  • Laatst online: 03-08-2024
Maar wat is nu de makkelijkste methode voor een "beginner" als ik? Het hoeft allemaal niet compatibel te zijn met andere programma's.

Verwijderd

/me denkt dat dan de ouderwetse ini file dan het makkelijkst is.

Hoef je voor de rest niets meer te doen.

  • Domokoen
  • Registratie: Januari 2003
  • Laatst online: 18-05 12:11
Oscar Mopperkont schreef op woensdag 15 december 2004 @ 22:59:
WriteInteger werkt toch alleen voor een ini file? Moet ik dus met ini files gaan werken?
Nee hoor. TFileStream heeft ook een method "WriteInteger" die precies doet wat je verwacht... er is ook een ReadInteger. Enig verschil is dat in een TFileStream de integer binair (in 4 bytes) wordt opgeslagen en in een INI file als tekst. Dit in combinatie met de ReadChunk/WriteChunk lijkt mij voldoende...

  • Oscar Mopperkont
  • Registratie: Februari 2001
  • Laatst online: 03-08-2024
Mr.Chinchilla schreef op zaterdag 18 december 2004 @ 11:25:
Nee hoor. TFileStream heeft ook een method "WriteInteger" die precies doet wat je verwacht... er is ook een ReadInteger. Enig verschil is dat in een TFileStream de integer binair (in 4 bytes) wordt opgeslagen en in een INI file als tekst. Dit in combinatie met de ReadChunk/WriteChunk lijkt mij voldoende...
Als ik de functie aanroep herkent Delphi hem echter niet. In welke unit zit die dan?

Verwijderd

Waarom zou je uberhaupt een writeinteger willen gebruiken? Je kunt toch gewoon write gebruiken?

Een Stream kent geen WriteInteger, een filestream ook niet.

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 12:05

Tomatoman

Fulltime prutser

Oscar Mopperkont schreef op zaterdag 18 december 2004 @ 16:41:
Als ik de functie aanroep herkent Delphi hem echter niet. In welke unit zit die dan?
Ik gok dat Mr.Chinchilla TRegIniFile en TFileStream door elkaar haalt.

Een goede grap mag vrienden kosten.


  • Oscar Mopperkont
  • Registratie: Februari 2001
  • Laatst online: 03-08-2024
Verwijderd schreef op vrijdag 17 december 2004 @ 19:47:
* Oscar Mopperkont denkt dat dan de ouderwetse ini file dan het makkelijkst is.

Hoef je voor de rest niets meer te doen.
Daar ben ik dus nu mee bezig en het lijkt makkelijk te werken. Alleen vraag ik mij nu af hoe ik de tekst uit de RichEdits in de inifile kan opslaan. De tekst is uit een RichEdit is namelijk een TStrings en je kunt alleen WriteString doen bij een ini-file.

Je kunt dan wel weer gaan werken met Richedit.Lines.Strings[i], maar hoe tel je dan het aantal strings? Ik zie geen count staan, en gaat de opmaak dan niet verloren? Als ik een zin heb die twee regels inneemt in de Richedit dan moet ik hem wegschrijven met Strings[0] en Strings[1], plaatst hij er dan geen enter tussen als ik het weer ga teruglezen?
Pagina: 1