Forum > General

saving and loading a changing record

(1/2) > >>

pyros:
Hello,

Within my application, I would like to save the users data to a file, and load it later.
My idea is to put everything that is going to be saved into one big record and write it.

When I add or remove any feature, the size and layout of the record will change.
When a file is loaded, I would like it to only write the fields of the records that match
those that existed when it was saved.

i.e. user saves record with:   title, height, width, color, levels
user loads into record with:   title, height, width, depth, shape, levels

so: title, height & levels will be overwritten, depth & shape will not.

I was thinking this may work with XML, but I have no idea how.
XMLPropStorage does something like this with a form, but not a record.

Any hints or suggestions would be welcome!

P.



Leledumbo:

--- Quote ---XMLPropStorage does something like this with a form, but not a record.
--- End quote ---
Correction: it does that with a form automatically, but nothing stops you from using it to save & load other values. Implement OnSaveProperties and OnRestoreProperties with your record values. Here's a code fragment from one of my projects:

--- Code: ---var
  CompTune: record
    LZMA, Force, Brute, UltraBrute: Boolean;
  end;
  Overlay: (olCopy, olStrip, olSkip);
  Additional: record
    AllMethods, AllFilters: Boolean;
  end;
  Other: record
    KeepBackup, SaveAsDef: Boolean;
  end;
  UPXThreadCount: Byte;

...

procedure TMainForm.UserPrefsRestoreProperties(Sender: TObject);
begin
  with UserPrefs do begin
    IniSection := 'AdvancedOpts';
    with CompTune do begin
      LZMA := ReadBoolean('LZMA', False);
      Force := ReadBoolean('Force', False);
      Brute := ReadBoolean('Brute', False);
      UltraBrute := ReadBoolean('UltraBrute', False);
    end;
    Ord(Overlay) := ReadInteger('Overlay', 0);
    with Additional do begin
      AllMethods := ReadBoolean('AllMethods', False);
      AllFilters := ReadBoolean('AllFilters', False);
    end;
    with Other do begin
      KeepBackup := ReadBoolean('KeepBackup', False);
      SaveAsDef := ReadBoolean('SaveAsDefault', False);
    end;
    UPXThreadCount := ReadInteger('ThreadCount', 1);
    IniSection := 'Preferences';
  end;
  TrackCompLvlChange(nil);
end;

procedure TMainForm.UserPrefsSaveProperties(Sender: TObject);
begin
  UserPrefs.IniSection := 'AdvancedOpts';
  if Other.SaveAsDef then
    with UserPrefs do begin
      with CompTune do begin
        WriteBoolean('LZMA', LZMA);
        WriteBoolean('Force', Force);
        WriteBoolean('Brute', Brute);
        WriteBoolean('UltraBrute', UltraBrute);
      end;
      WriteInteger('Overlay', Ord(Overlay));
      with Additional do begin
        WriteBoolean('AllMethods', AllMethods);
        WriteBoolean('AllFilters', AllFilters);
      end;
      WriteBoolean('KeepBackup', Other.KeepBackup);
      WriteInteger('ThreadCount', UPXThreadCount);
    end;
  with UserPrefs do begin
    WriteBoolean('SaveAsDefault', Other.SaveAsDef);
    IniSection := 'Preferences';
  end;
end;

--- End code ---

Btw, I'm using TIniPropStorage instead of TXMLPropStorage because I expect the configuration to be easily modified by hand, but the usage is similar.

pyros:
Thanks for the handy reply Leledumb. Using an .ini file seems a nice way to do it.

I was hoping to find a method that didn't require individually reading and writing
each item manually, as well as not minding if the file and record did not match.

I have attempted and failed this in two ways now.

The first was to create a class of TComponent with each 'record field' as a published
property, then create an instance in the TForm1 definition and see if I could add it to
the SessionProperties. That doesn't work.

Next I tried creating my own TComponent containing the values I wanted to save,
as before. Then I write and read it to/from a stream. So far so good. However when
the TComponant changes, the program gets upset. No worries I think, I'll just put
in my own error handler to stop it getting upset. Alas, it still fails to read any fields
into the TComponent type class from the mismatching file.

I think I'll give up on that now and do it your way. Yes, each item must be manually
written and read, but I've run out of options and enthusiasm to automate this and
I certainly don't want to rewrite the TReader class.

Just for the record, here's the working but non-effective code:


--- Code: ---
type
  TSettings1 = class(TComponent)
  private
    fText: String;
fNumber: Integer;
  published
property Number: Integer read fNumber write fNumber;
    property Text: String read fText write fText;
  end;

type
  TSettings2 = class(TComponent)
  private
    fCount: Integer;
fNumber: Integer;
  published
property Number: Integer read fNumber write fNumber;
property Count: Integer read fCount write fCount;
  end;


procedure TForm1.Button1Click(Sender: TObject);
var
Settings1: TSettings1;
Stream: TFileStream;
begin
Settings1 := TSettings1.Create(Self);
Settings1.Text := 'the jungle book';
Settings1.Number := 321;

Stream:=TFileStream.Create('c:\teststream.str', fmCreate);;
Stream.WriteComponent(Settings1);
Stream.Free;

Settings1.Free;
end;


procedure TForm1.HandleReadErrors(Reader: TReader; const Message: string; var Handled: Boolean);
begin
try
Handled:=true;
except
on E:Exception Do Showmessage('strm= '+E.message);
end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
Settings2: TSettings2;
Stream: TFileStream;

Reader: TReader;
begin
Settings2 := TSettings2.Create(Self);

Settings2.Count := 99;
Settings2.Number := -5;

Stream := TFileStream.Create('c:\teststream.str', fmOpenRead);
Reader := TReader.Create(Stream, 4096);
Reader.OnError := @HandleReadErrors;

try
Reader.ReadComponent(Settings2);
except
on E:Exception Do Showmessage('strm= '+E.message);
end;

Stream.Free;

ShowMessage(IntToStr(Settings2.Count) + ' - ' + IntToStr(Settings2.Number));
Settings2.Free;
end;

--- End code ---

Bart:
Why not use a plain old TIniFile for it?

There is an xml tutorial on the wiki if you want xml.

Bart

pyros:
Bart,

there is no reason to not use a plain old TIniFile; the format is irrelevant to the problem.

However, I do not know of a function that will write any record or class to a TIniFile
(or any other type of file) and then read it back into a changed version of that record
or class (when it is changed in later versions of the application).

Navigation

[0] Message Index

[#] Next page

Go to full version