Newsgroup: it.comp.lang.delphi
Titolo: Salvare una variabile su file (guisto x avere le idee chiare...)

Salve a tutti!
Nel programma che sto scrivendo uso una variabile piuttosto complessa
(record di record di vari tipi, con array dinamici in mezzo), e mi
chiedevo se fosse possibile salvarla in maniere veloce e automatica su
file per poi ricaricarla.
Spulciando un po' il NG ho visto che una soluzione potrebbe essere
derivare da TPersistent e poi usare funzioni di streaming su file tipo
WriteComponent, ma la cosa mi pare cmq complessa (dovrei convertire la
mia variabile record in una classe) e poco chiara, almeno per me:
1. WriteComponent ha come argomento TComponent, allora dovrei derivare
da TComponent e non da TPersistent, guisto? O c'e' qualche alternativa?
2. Esistono diversi metodi WriteComponent in diverse classi... quale fa
al caso mio? L'help di Delphi non e' molto prodigo di aiuti su questo
argomento...
3. La struttura "record di record di ecc" della mia variabile da
salvare deve diventare "classe con proprieta' classe con proprieta'
ecc" o solo "classe con proprieta' record di ecc'? (spero di essere
stato chiaro...)
Ringrazio tutti in anticipo!
Bye!
PS: Ringrazio anche tutti quelli che hanno risposto meravigliosamente
ad un mio precedente messaggio sulla scrittura di un programma 'a
pagine'. Scusate il ritardo ma mi sono messo solo ora al lavoro ;-)

TPersistent ok. Ma vedi anche Treader e TWriter.
record, variabili.. uff.
xche fare fatica con questa roba(ccia) quando le classi sono cosi 
comode, eleganti, portabili, riutilizzabili, facilmente manutenibili etc 
etc etc.
A.

 etc etc.
Beh... devo trattare (con calcoli matematici anche complessi)
principalmente dati numerici, e quindi una struttura a record va piu'
che bene... solo non posso salvarla...

pagine'. Scusate il ritardo ma mi sono messo solo ora al lavoro ;-)
Hai provato a pensare ai Files of type?
Per i dati valore (anche composti come Array e Record) funziona benissimo.
Ciao
Luca Bertoncini
--- posted by geoForum on 
http://www.delphiedintorni.it

pagine'. Scusate il ritardo ma mi sono messo solo ora al lavoro ;-)


pagine'. Scusate il ritardo ma mi sono messo solo ora al lavoro ;-)


 che bene... 
Se cosi fosse, non avresti il problema che hai.
Quindi, ihmo, NON VA BENE.
A parte cio continuo a chiedermi cosa ha un tipo record per essere 
preferito ad una istanza di una classe. O cosa ha una classe di cosi 
spaventoso da non essere preferita ad un tipo record.
Che dire poi di quei linguaggi come c++ e c# nei quali questa 
distinzione o non esiste o è puramente formale?
A.

 Per i dati valore (anche composti come Array e Record) funziona benissimo.
Se ne è parlato su "About Delphi Programming" proprio questa settimana 
in un articolo:
http://delphi.about.com/od/fileio/a/fileof_delphi.htm
Ciao,
   Marco.
-- 
MARCO BREVEGLIERI
  (
http://www.marco.breveglieri.name
)
     Non hai la possibilità di scaricare Turbo Delphi Explorer?
     Te lo spedisco io!! [
http://www.marco.breveglieri.name/?p=48
]

 file per poi ricaricarla.
prova anche a dare un'occhiata a un mio post del 22-9-2006 nel thread 
"Dubbi (probabilmente da nubbio) su struttura di una applicazione"
-- 
Mauro Falzari
"Rosa, ella ha vissuto ciň che vivono le rose,
Lo spazio d'un mattino".
  (François de Malherbe, 1555-1628)

 spaventoso da non essere preferita ad un tipo record.
Beh, nel mio caso ti posso dire che principalmente e' la poca
dimestichezza con le classi, cosa a cui sto cercando di rimediare il
piu' possibile :-)

 Per i dati valore (anche composti come Array e Record) funziona benissimo.
No, non va bene, il mio record e' troppo incasinato: e' un singolo
record con dentro array dinamici, valori, stringhe, altri record...
dovrei srivere un sacco di codice per gestire un salvataggio del
genere, non ne vale la pena, piuttosto non implemento la possibilita'
di salvataggio nel programma... :-(

 "Dubbi (probabilmente da nubbio) su struttura di una applicazione"
Eh eh, lo conosco a memoria quel thred, l'ho iniziato io ;-))
Cmq, immagino tu ti riferisca al post in cui parli del salvataggio
delle opzioni in XML, giusto?
Pero' io vorrei salvare in binario, e senza dover cambiare troppo il
codice gia' scritto (che non e'  poco, visto che questo prog e'
l'evoluzione di un prog precedente)...
...devo quindi dedurre che non ci sia un metodo "relativamente poco
impegnativo" per salvare una struttura complessa come quella che ho tra
le mani?...

 l'evoluzione di un prog precedente)...
ok, mi era sfuggito.
Per "salvare in binario" ti posso postare del codice "veramente as-is" (mi 
vergogno quasi), che avevo realizzato quando ero proprio agli inizi. 
Però funziona ed esegue lo streaming in lettura/scrittura, in formato 
"raw", di una lista di oggetti. Magari ti può servire come spunto per 
qualcosa...
unit uClasses;
interface
uses
  Classes, Contnrs;
procedure SaveList(const AFilename: string; lstObj: TObjectList);
procedure LoadList(const AFilename: string; lstObj: TObjectList);
type
  TMain = class(TPersistent)
  public
    ID: Integer;
    Name: string;
    Items: TObjectList;   //che conterrà solo oggetti TItem
    procedure Load(Reader: TReader);
    procedure Save(Writer: TWriter);
    constructor Create;
    destructor Destroy; override;
  end;
  TItem = class(TPersistent)
  public
    Date: TDateTime;
    Text: string;
    Exists: Boolean;
    procedure Load(Reader: TReader);
    procedure Save(Writer: TWriter);
  end;
implementation
uses SysUtils;
procedure SaveList(const AFilename: string; lstObj: TObjectList);
var
  Stream: TFileStream;
  Writer: TWriter;
  i: Integer;
begin
  Writer:= nil;     //perchè nil???
  if FileExists(AFilename) then DeleteFile(AFilename);
  Stream:= TFileStream.Create(AFilename, fmCreate);
  try
    Writer:= TWriter.Create(Stream, 4096); //perchè 4096 cioè 2 alla 12?
    with Writer do
    begin
      WriteListBegin;
      for i:= 0 to lstObj.Count - 1 do
        TMain(lstObj[i]).Save(Writer);
      WriteListEnd;
    end
  finally
     Writer.Free;
     Stream.Free;
  end;
end;
procedure LoadList(const AFilename: string; lstObj: TObjectList);
var
  Stream: TFileStream;
  Reader: TReader;
  i: Integer;
  AMain: TMain;
begin
  Reader:= nil;
  if not FileExists(AFilename) then Exit;
  Stream:= TFileStream.Create(AFilename, fmOpenRead);
  try
    Reader:= TReader.Create(Stream, 4096); //perchè 4096 cioè 2 alla 12?
    with Reader do
    begin
      ReadListBegin;
      while not EndOfList do
      begin
        AMain:= TMain.Create;
        AMain.Load(Reader);
        lstObj.Add(AMain);
      end;
      ReadListEnd;
    end;
  finally
     Reader.Free;
     Stream.Free;
  end;
end;
procedure TMain.Save(Writer: TWriter);
var
  i: Integer;
begin
  with Writer do
  begin
    WriteInteger(ID);
    WriteString(Name);
    WriteListBegin;
    if Assigned(Items) then
      for i:= 0 to Items.Count - 1 do
        TItem(Items[i]).Save(Writer);
    WriteListEnd;
  end;
end;
procedure TMain.Load(Reader: TReader);
var
  AnItem: TItem;
begin
  with Reader do
  begin
    ID:= ReadInteger;
    Name:= ReadString;
    ReadListBegin;
    while not EndOfList do
    begin
      AnItem:= TItem.Create;
      AnItem.Load(Reader);
      Items.Add(AnItem);
    end;
    ReadListEnd;
  end;
end;
procedure TItem.Save(Writer: TWriter);
begin
  with Writer do
  begin
    WriteDate(Date);
    WriteString(Text);
    WriteBoolean(Exists);
  end;
end;
procedure TItem.Load(Reader: TReader);
begin
  with Reader do
  begin
    Date:= ReadDate;
    Text:= ReadString;
    Exists:= ReadBoolean;
  end;
end;  
constructor TMain.Create;
begin
  Items:= TObjectList.Create;
end;
destructor TMain.Destroy;
begin
  Items.Free;
  inherited Destroy;
end;
end.
-- 
Mauro Falzari
"Non è morto ciò che in eterno può attendere,
e col passar di strane ere, anche la morte muore".
  (Howard Phillips Lovecraft, 1890-1937)

 [...]
Grazie mille, direi che il tuo esempio mi ha chiarito molto le idee
rigurado al salvataggio di oggetti, ma ho anche capito che non e' una
cosa velocemente implementabile come invece speravo... in quanto (se ho
ben interpretato) bisogna cmq scriversi a manina la procedura di
salvataggio e caricamento di ogni tipo di oggetto, mentre io speravo
esistesse in delphi qualcosa di piu' "automatico" (forse speravo troppo
;-p ...)
Ancora grazie, mi salvo questo tuo esempio e lo riprendero' in mano
sicuramente quando avro' tempo di rimeggiare il mio codice!

Mi e' sorto solo un dubbio leggendo il tuo codice... conviene sempre
usare un TObjectList piuttosto che un banalissimo array? Alla fine
hanno funzionalita' molto simili, giusto?



Sě. In effetti non ricordo neppure io perchč avevo usato TObjectList. Forse 
perchč allora non sapevo nulla dell'esistenza delle liste type-safe :-)
-- 
Mauro Falzari
"Io passo in mezzo agli uomini,
come in mezzo a frammenti dell'avvenire:
di quell'avvenire che io contemplo".
  (Friedrich Wilhelm Nietzsche, 1844-1900)

 perchè allora non sapevo nulla dell'esistenza delle liste type-safe :-)
Azz, cosi' impazziro'... cosa sono le liste type-safe?
E poi un' altra domanda... cosa sarebbe successo se nel tuo esempio
invece che derivare da TPersistent avessi creato una classe da zero?
Non mi pare che nel codice sia usata alcuna funzione della classe
TPersistent...

 TPersistent...
no, infatti credo di averla usata solo perchč "TPersistent is the ancestor 
for all objects that have assignment and streaming capabilities", e io 
volevo appunto lo streaming di un oggetto (ovvero quella ora chiamerei la 
persistenza). Per essere precisi, ricordo che il codice l'avevo scritto con 
aiuti ricevuti (mi pare anche da Luigi D. Sandon) sul gruppo di discussione 
dedicato a delphi che esisteva sul server di Marco Cantů 
(
news.marcocantu.it
), server ora dismesso - lo sostituisce 
news.delphiedintorni.it
L'archivio dei vecchi post non credo sia stato piů recuperato, nč č 
indicizzato da google. 
====================================
Esempio #1 - derivare da TCollection
====================================
type
  TMyCollectionItem = class(TCollectionItem)
  public
    Name: string;     //public field
    Date: TDateTime;  //public field
  end;
  TMyCollection = class(TCollection)
  protected
    function GetItem(Index: Integer): TMyCollectionItem;
    procedure SetItem(Index: Integer; Value: TMyCollectionItem);
  public
    constructor Create;
    function Add: TMyCollectionItem;
    property Items[Index: Integer]: TMyCollectionItem read GetItem write 
SetItem; default;
  end;
{ TMyCollection }
function TMyCollection.Add: TMyCollectionItem;
begin
  Result:= TMyCollectionItem(inherited Add);
end;
constructor TMyCollection.Create;
begin
  inherited Create(TMyCollectionItem);
end;
function TMyCollection.GetItem(Index: Integer): TMyCollectionItem;
begin
  Result:= TMyCollectionItem(inherited GetItem(Index))
end;
procedure TMyCollection.SetItem(Index: Integer; Value: TMyCollectionItem);
begin
  inherited SetItem(Index, Value);
end;
====================================
Esempio #2 - derivare da TObjectList
====================================
type
  TProduct = class
  private
    FID: Integer;
    FName: string;
  public
    destructor Destroy; override;
    property ID: Integer read FID write FID;
    property Name: string read FName write FName;
  end;
  TProducts = class(TObjectList)
  protected
    function GetProduct(const Index: Integer): TProduct;
    procedure SetProduct(const Index: Integer; Item: TProduct);
  public
    destructor Destroy; override;
    function Add(Item: TProduct): Integer;
    procedure Insert(const Index: Integer; Item: TProduct);
    property Objects[const Index: Integer]: TProduct read GetProduct write 
SetProduct; default;
  end;
{ TProduct }
destructor TProduct.Destroy;
begin
  Writeln('Messaggio generato dal distruttore di un ' + ClassName);
  inherited Destroy;
end;
{ TProducts }
function TProducts.Add(Item: TProduct): Integer;
begin
  Result:= inherited Add(Item);
end;
destructor TProducts.Destroy;
begin
  inherited Destroy;
  Writeln('Oggetto di classe ' + ClassName + ' distrutto');  
end;
function TProducts.GetProduct(const Index: Integer): TProduct;
begin
  Result:= TProduct(inherited GetItem(Index))
end;
procedure TProducts.Insert(const Index: Integer; Item: TProduct);
begin
  inherited Insert(Index, Item)
end;
procedure TProducts.SetProduct(const Index: Integer; Item: TProduct);
begin
  inherited SetItem(Index, Item)
end;
=============================
Esempio #3 - wrapper di TList
=============================
unit DateTimeList;
interface
uses
  Classes;
type
  TDateTimeList = class
  private
    FList: TList;
    function GetItem(Index: Integer): TDateTime;
    procedure SetItem(Index: Integer; Value: TDateTime);
    function GetCount: Cardinal;
  protected
  public
    constructor Create;
    destructor Destroy; override;
    function Add(Value: TDateTime): Cardinal;
    procedure Insert(Index: Integer; Value: TDateTime);    
    procedure Delete(Index: Integer);
    procedure Clear;
    function IndexOf(Value: TDateTime): Integer;
    procedure Sort;
    property Items[Index: Integer]: TDateTime read GetItem write SetItem; 
default;
    property Count: Cardinal read GetCount;
  end;
implementation
{ TDateTimeList }
function TDateTimeList.Add(Value: TDateTime): Cardinal;
var
  P: PDateTime;
begin
  New(P);
  P^:= Value;
  Result:= FList.Add(P);
end;
procedure TDateTimeList.Clear;
var
  i: Integer;
begin
  for i:= 0 to Pred(FList.Count) do
    Dispose(PInteger(FList.Items[i]));
  FList.Clear;
end;
constructor TDateTimeList.Create;
begin
  inherited Create;
  FList:= TList.Create;
end;
procedure TDateTimeList.Delete(Index: Integer);
begin
  Dispose(PDateTime(FList.Items[Index]));
  FList.Delete(Index);
end;
destructor TDateTimeList.Destroy;
begin
  Clear;
  FList.Free;
  inherited Destroy;
end;
function TDateTimeList.GetCount: Cardinal;
begin
  Result:= FList.Count;
end;
function TDateTimeList.GetItem(Index: Integer): TDateTime;
begin
  Result:= PDateTime(FList.Items[Index])^;
end;
function TDateTimeList.IndexOf(Value: TDateTime): Integer;
var
  i: Integer;
begin
  Result:= -1;
  for i:= 0 to Pred(GetCount) do
    if GetItem(i) = Value then
    begin
      Result:= i;
      Break;
    end;
end;
procedure TDateTimeList.Insert(Index: Integer; Value: TDateTime);
var
  P: PDateTime;
begin
  New(P);
  P^:= Value;
  FList.Insert(Index, P);
end;
procedure TDateTimeList.SetItem(Index: Integer; Value: TDateTime);
begin
  PDateTime(FList.Items[Index])^:= Value;
end;
function Compare(Item1, Item2: Pointer): Integer;
{ Compare is a comparison function that indicates how the items are to be 
ordered.
  Compare returns < 0 if Item1 is less and Item2,
  0 if they are equal and
  > 0 if Item1 is greater than Item2. }
begin
  if TDateTime(Item1^) = TDateTime(Item2^) then
    Result:= 0
  else
    if TDateTime(Item1^) < TDateTime(Item2^) then
      Result:= -1
    else
      Result:= 1;
end;
procedure TDateTimeList.Sort;
begin
  FList.Sort(Compare);  //= FList.Sort(@Compare)
end;
end.
-- 
Mauro Falzari
"La teoria č quando si sa tutto ma non funziona niente. 
La pratica č quando funziona tutto ma non si sa il perchč. 
In ogni caso si finisce sempre a coniugare la teoria con la 
pratica: 
non funziona niente e non si sa il perchč".
  (Albert Einstein)

chiedo scusa, ho cannato l'esempio #3, riporto questo che è più calzante e 
più semplice:
unit FormList;
interface
uses
  Classes, Forms;
type
  TFormList = class
  private
    FList: TList;
    function GetItems(I: Integer): TCustomForm;
    procedure SetItems(I: Integer; Value: TCustomForm);
  protected
  public
    constructor Create;
    destructor Destroy; override;
    function Add(Form: TCustomForm): Integer;
    function IndexOf(Form: TCustomForm): Integer;
    procedure Delete(Form: TCustomForm);
    procedure Clear;
    function Count: Integer;
    procedure Assign(List: TFormList);
    property Items[I: Integer]: TCustomForm read GetItems write SetItems; 
default;
  end;
implementation
uses
  SysUtils{, Windows};
{ TFormList }
function TFormList.Add(Form: TCustomForm): Integer;
begin
  Result := FList.Add(Form);
end;
procedure TFormList.Assign(List: TFormList);
begin
   ....
end;
procedure TFormList.Clear;
begin
  FList.Clear;
end;                                           
function TFormList.Count: Integer;
begin
  Result := FList.Count
end;
constructor TFormList.Create;
begin
  FList := TList.Create;
end;
procedure TFormList.Delete(Form: TCustomForm);
var
  i: Integer;
begin
  i := IndexOf(Form);
  if i > -1 then
    FList.Delete(i);
end;
destructor TFormList.Destroy;
begin
  FList.Free;
  inherited;
end;
function TFormList.GetItems(I: Integer): TCustomForm;
begin
  Result := TCustomForm(FList[I])
end;
function TFormList.IndexOf(Form: TCustomForm): Integer;
begin
  Result := FList.IndexOf(Form)
end;
procedure TFormList.SetItems(I: Integer; Value: TCustomForm);
begin
  FList[I] := Value
end;
end.
-- 
Mauro Falzari
"La teoria è quando si sa tutto ma non funziona niente. 
La pratica è quando funziona tutto ma non si sa il perchè. 
In ogni caso si finisce sempre a coniugare la teoria con la 
pratica: 
non funziona niente e non si sa il perchè".
  (Albert Einstein)

Grazie ancora!
...e se facessi un wrapper di TObjectList? Non sono pratico coi
puntatori e per ora preferisco star loro molto lontano :-)
Un ultima domandi na ancora... come mai chiami delle funzioni inherited
quando hai definito una classe che non eredita da niente?

 quando hai definito una classe che non eredita da niente?
se ti riferisci a questo
destructor TFormList.Destroy;
begin
  FList.Free;
  inherited;
end;
..certo che eredita: da TObject!
La scrittura 
  TFormList = class
è equivalente a
  TFormList = class(TObject)
Infatti se scrivi
destructor TFormList.Destroy;
begin
  FList.Free;
  inherited Destroy;
end;
e "segui il codice" da Destroy (CTRL+CLIC), puoi vedere tu stesso...
-- 
Mauro Falzari
- "Vorrei del dentifricio".
- "Abbiamo Mentadent, Sensodent o Pepsodent, quale preferisce?"
- "Senzadent".
  (da "Maakies", di Tony Millionaire, 1995)

   TFormList = class(TObject)
Ah, ecco, mi mancava questo passaggio :-)

file per poi ricaricarla...
Ciao,
bellissimo il post di Mauro: sicuramente è ok.
Ma, tu hai detto di usare record, la cui struttura deve essere 
sicuramente impostata da te inserendola nel codice del programma; ergo, 
la struttura finale, per quanto complicata possa essere, ha una forma 
ben determinata e conosciuta, restando sconosciuta soltanto la quantità 
dei dati inseriti negli array dinamici.
Per salvare i dati inseriti, puoi usare un semplicissimo file di 
tipo .ini ed i relativi comandi di creazione e gestione delle sezioni: 
la sezione iniziale [INIZIO] potrebbe contenere, nei tag, alcuni valori 
(esempio il n° di elementi) dei record oppure il nome dei singoli 
record; ogni sezione successiva [s1], [s2] potrebbe essere intestata ad 
un tag di [INIZIO] ed i singoli suoi tag (numerati, per esempio, secondo 
i campi del record) potrebbero contenere un valore del campo-record 
oppure un tag potrebbe contenere un nome di sezione intestata ad un 
array collegato o ad un altro record a cui saltare, definito più oltre 
come singola sezione; nel valore collegato ad un tag è possibile 
inserire tutta una lista di valori, separati da doppio apice (") per 
poter contenere la virgola, lista leggibile con una TStringList 
che "succhierebbe" direttamente i valori (in formato string) rendendoli 
disponibili e prelevabili con una iterazione for; e così via complicando.
Ne verrebbe fuori una lista incomprensibile ad occhio umano ma ben 
pianificabile e leggibile con del codice che, in sede di salvataggio, 
svuota il file e lo riscrive di sana pianta ogni volta, e che, in sede 
di lettura, ricrea tutta la situazione salvata, riempendo i record (che 
devono già esistere in procedura) e creando la struttura degli arraies 
dinamici.
Puoi anche creare un file contenente la struttura di tutte le strutture 
create dalla procedura ed un altro file contenente sezioni intestate al 
singolo componente della struttura, con tag contenente la lista di 
elementi del singolo componente della struttura.
Puoi anche creare delle copie nel tempo e visualizzarne il contenuto in 
griglie apposite per verificare il procedere del lavoro che, in quanto a 
base di matematica, comporta (per mia esperienza personale) un continuo 
controllo da dove sono venuto, dove sono e dove intendo andare.
Naturalmente, creare un file (io creo delle procedure-utility apposite) 
di controllo comporta più tempo che creare la procedura finale, ma 
questo mi ripaga per sicurezza e per guadagno di tempo nel futuro.
Quanto sopra mi sembra più appropriato (anche se più grezzo di quello 
che ha scritto Mauro) perché deve soltanto elencare dei valori da 
riprendere in modo appropriato.
Ciao e buon Natale.
NioBi
