Recent

Author Topic: save open records to disk  (Read 17767 times)

clemmi

  • Jr. Member
  • **
  • Posts: 54
save open records to disk
« on: March 14, 2014, 07:06:14 pm »
Is there a way to save a record to disk to be later on read from disk.

I know how to save the text from a memo, but I wonder if there's a way to do the same with a record or an array of records without going through the memo. Like be able to update the array of records from a previous save.

I found a Delphi reference using a stream but it seems to be for a console setup (strm.write/read, etc.) not a for GUI application like the one I'm working on. Maybe there's a way to adapt it. Any references available?

The idea is to save stages of a scheduling program at various times without having to reenter and process the data again.
Thanks.
_cl

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: save open records to disk
« Reply #1 on: March 14, 2014, 07:23:20 pm »
Show us the code, specifically I want to see the record definition and any code you have tested so far. As for your question the answer is yes it is possible to save a record on disk and later read it.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

clemmi

  • Jr. Member
  • **
  • Posts: 54
Re: save open records to disk
« Reply #2 on: March 16, 2014, 06:10:59 pm »
This is the reference I found:
http://stackoverflow.com/questions/3820996/delphi-2010-how-to-save-a-whole-record-to-a-file

I don't know how to use it on a GUI application. It uses strm.write and strm.read  which seems to be console code. I think it need to be use with Open/SaveDialogs in GUI application. Not sure how stream works.

You say: "As for your question the answer is yes it is possible to save a record on disk and later read it.".

Do you have a sample code? I should be able to take from that to my specific application.
thanks!
-cl

Blaazen

  • Hero Member
  • *****
  • Posts: 2782
  • POKE 54296,15
    • Eye-Candy Controls
Re: save open records to disk
« Reply #3 on: March 16, 2014, 06:42:10 pm »
IMO good example on that page is down ("myFile   : File of TCustomer;"). It is typical "file of records".

Quote
I think it need to be use with Open/SaveDialogs in GUI application.
There is no difference in save/load files in console/GUI applications. After all, Open/SaveDialogs only selects a filename and path, they don't save/load anything themselves.
Lazarus 2.1.0 r59757M FPC 3.3.1 r40507 x86_64-linux-qt Chakra, Qt 4.8.7/5.11.2, Plasma 5.14.2
Lazarus 1.8.2 r57369 FPC 3.0.4 i386-win32-win32/win64 Wine 3.21

Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: save open records to disk
« Reply #4 on: March 16, 2014, 07:16:34 pm »
ok here are 2 different approaches.

1) a pascal typed file the most common pre 1990 pascal database format and one of the reasons pascal was popular back then.
Code: [Select]
type
  PMyRecord = ^TMyRecord;
  TMyRecord = record
    ID    : cardinal;
    FName : string[20];
    LName : string[80];
    BDay  : TDateTime;
  end;
  TMyRecordFile = file of TMyRecord;
  TMyRecordArray = array of TMyRecord;

procedure AppendMyRecord(const aRec:PMyRecord; aFilename:string);
function OpenMyRecFile(const aFilename:String):TMyRecordFile;
procedure CloseMyRecordFile(var aFile:TMyRecordFile);
procedure NextRecord(var aRec:TMyRecord; var aFile:TMyRecordFile);
function LoadAllRecords(const aFileName:String):TMyRecordArray;
procedure SaveRecords(Const aFilename:String; var aRecords : TMyRecordArray);
implementation

procedure SaveRecords(Const aFilename:String; var aRecords : TMyRecordArray);
var
  vFile : TMyRecordFile;
  vCntr : Integer;
begin
  AssignFile(vFile, aFileName);
  Rewrite(vFile);
  for vCntr := Low(aRecords) to High(aRecords) do begin;
    Write(vFile, aRecords[vCntr]);
  end;
end;



function LoadAllRecords(const aFileName:String):TMyRecordArray;
var
  vFile : TMyRecordFile;
  vBuf  : TMyRecord;
begin
  AssignFile(vFile, aFileName);
  Reset(vFile);
  while not EOF(vFile) do begin
    Read(vFile, vBuf);
    SetLength(Result, Length(Result)+1);
    Result[High(Result)] := vBuf;
  end;
end;

procedure CloseMyRecordFile(var aFile:TMyRecordFile);
begin
  CloseFile(aFile);
end;

function OpenMyRecFile(const aFilename:String):TMyRecordFile;
begin
  AssignFile(Result,aFilename);
  Reset(Reset);
end;

procedure AppendMyRecord(const aRec:PMyRecord; aFilename:string);
var
  vFile : File of TMyRecord;
  vTemp : TMyRecord;
begin
  AssignFile(vFile,aFilename);
  Reset(vFile);
  try
    while not EOF(vFile) do Read(vFile, vTemp);
    Write(vFile,aRec^);
  finally
    CloseFile(vFile);
  end;
end;

procedure NextRecord(var aRec:TMyRecord; var aFile:TMyRecordFile);
begin
  if not eof(aFile) then Read(aFile,aRec);
end;
2) a simple stream based load/save mechanism a more modern mechanism which should have better future than an untyped file.

Code: [Select]
type
  PMyRecord = ^TMyRecord;
  TMyRecord = record
    ID    : cardinal;
    FName : string[20];
    LName : string[80];
    BDay  : TDateTime;
  end;
  TMyRecordArray = array of TMyRecord;

function LoadAll(const aFilename:String):TMyRecordArray;
function LoadAll_(const aFilename:String):TMyRecordArray;
procedure SaveAll(const aFileName:String; var aRecords:TMyRecordArray);

implementation
uses math;

{load them all in single read operation}
function LoadAll(const aFilename:String):TMyRecordArray;
var
  vFile:TFileStream;
begin
  vFile := TFileStream.Create(aFilename, fmOpenReadWrite or fmShareExclusive);
  try
    vFile.Seek(0,0);
    SetLength(Result, ceil(vFile.Size / SizeOf(TMyRecord)) );
    vFile.Read(Result[Low(result], vFile.Size);
  finally
    vFile.Free;
  end;
end;

{load them all one by one.}
function LoadAll_(const aFilename:String):TMyRecordArray;
var
  vFile:TFileStream;
begin
  vFile := TFileStream.Create(aFilename, fmOpenReadWrite or fmShareExclusive);
  try
    vFile.Seek(0,0);
    while vFile.Position < vfile.Size do begin;
      SetLength(Result, Length(Result)+1);
      vFile.Read(Result[high(Result]), SizeOf(TMyRecord));
    end;
  finally
    vFile.Free;
  end;
end;

//save all in a single write operation
procedure SaveAll(const aFileName:String; var aRecords:TMyRecordArray);
var
  vFile  : TFileStream = nil;
  vTotal : cardinal = 0;
begin
  vFile := TFileStream.Create(aFilename, fmOpenReadWrite or fmShareExclusive);
  try
    vTotal := SizeOf(TMyRecord) * (High(aRecords)-Low(aRecords));
    vFile.Size := vTotal; //avoid appending byte by byte just allocate the hall thing if possible.
    vFile.Seek(0,0);
    vFile.Write(Result[Low(result], vTotal);
  finally
    vFile.Free;
  end;
end;
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

clemmi

  • Jr. Member
  • **
  • Posts: 54
Re: save open records to disk
« Reply #5 on: March 17, 2014, 12:44:00 am »
thanks - I'll try and see if I can get it going...
-cl

clemmi

  • Jr. Member
  • **
  • Posts: 54
Re: save open records to disk
« Reply #6 on: March 17, 2014, 01:25:44 pm »
I get this error:
saverecordsunit1.pas(104,24) Error: Identifier not found "Result"
saverecordsunit1.pas(104,35) Error: Identifier not found "result"

This is the line, in procedure SaveAll:
vFile.Write(Result[Low(result)], vTotal);   

note: seems to be  a  ")" missing at end of 'result' (same in function LoadAll).

Is 'result' a default in functions only, not inprocedures...?
-cl



taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: save open records to disk
« Reply #7 on: March 17, 2014, 01:33:06 pm »
vFile.Write(Result[Low(result)], vTotal);   

note: seems to be  a  ")" missing at end of 'result' (same in function LoadAll).

Is 'result' a default in functions only, not in procedures...?
Correct it does miss a ")" at the point you placed it and also it should be aRecords instead of Result, result is a function only variable.

That code was typed inside my browser and the editor is not very comfortable so you might find more discrepancies.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

clemmi

  • Jr. Member
  • **
  • Posts: 54
Re: save open records to disk
« Reply #8 on: March 17, 2014, 06:17:06 pm »
taazz,
I like your code but I have problems implementing it.
I copied the code without errors. I tried several ways but I can't get t right.

I want to define an array of records, save it to disk via SaveDialog so I can give it a name.
And then use OpenDialog to get it back.

Something like:
-------------------------------------------------
procedure TForm1.ButtonSAVEClick(Sender: Object):
begin
if OpenDialog1.execute then
begin
rPathNNameSave := SaveDialog1.filename;
===>   something.savetofile(rPathNNameSave);   <=====
end;
end;
---------------------------------

why is "aFilename" a const ?
---------------------------------

These are a few things I tried and didn't work:

"TMyRecordArray = array of TMyRecord;"    is in the "type" section.
when I try in var "TMyRecordArray : array[0..5] of TMyRecord;"
it says Error: Duplicate identifier "TMyRecordArray"
when I try var "arMyRecord : array[0..5] of TMyRecord;"
then I can't use it in buttonClick as "TForm1.SaveAll(fileName, arMyRecord);
gives: "saverecordsunit1.pas(101,38) Error: Only class class methods, class properties and class variables can be accessed in class methods "
-----------------------------------

close but going in circles...

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: save open records to disk
« Reply #9 on: March 17, 2014, 06:45:52 pm »
taazz,
I like your code but I have problems implementing it.
OK.

I copied the code without errors. I tried several ways but I can't get t right.
Get what right exactly?

I want to define an array of records, save it to disk via SaveDialog so I can give it a name.
And then use OpenDialog to get it back.

Something like:
-------------------------------------------------
procedure TForm1.ButtonSAVEClick(Sender: Object):
begin
if OpenDialog1.execute then
begin
rPathNNameSave := SaveDialog1.filename;
===>   something.savetofile(rPathNNameSave);   <=====
end;
end;
you didn't look at my code at all correct? You simple copied as it was verbatim probably changed the record definition to your own needs at maximum, if you had looked at the code you yould have noticed the 2 procedures that show the code to save and read the data, you can call them like this
Code: [Select]
procedure TestSave;
var
  vList : TMyrecordArray;
  vCntr : integer;
  procedure InitRec(var aRec:TMyRecord);
  begin
     arec.ID := 0;
     aRec.Fname := '';
     aRec.LName := '';
     aRec.BDay := 0;
  end;
begin
  setlength(vlist,10);
  for vCntr := low(vList) to High(vList) do begin
    initRec(vlist[vCntr]);
    vlist[vCntr].ID := 100+vCntr;
  end;
  if Savedialog1.Execute then saveall(Savedialog1.Filename, vList);
  setLength(vList,0);
end;
procedure TestLoad;
var
  vList : TMyrecordArray;
  vCntr : integer;
begin
  if Opendialog1.Execute then vlist := loadall(Opendialog1.Filename);
  for vCntr := low(vList) to High(vList) do
    ShowMessage('Pos '+inttostr(vCntr)+' ID : '+inttostr(vList[vCntr].ID));
  setLength(vList,0);//free the memory.
end;


---------------------------------

why is "aFilename" a const ?
---------------------------------
1- because there is no need to make it "by reference: and I avoid "by value" parameters unless there is a reason to use them so const the default choice for a parameter for me also it makes it possible to use a property as value directly instead of copying the property's value to a variable and pass that to the procedure.


These are a few things I tried and didn't work:

"TMyRecordArray = array of TMyRecord;"    is in the "type" section.
when I try in var "TMyRecordArray : array[0..5] of TMyRecord;"
it says Error: Duplicate identifier "TMyRecordArray"

Isn't this already declared in my code?


when I try var "arMyRecord : array[0..5] of TMyRecord;"
then I can't use it in buttonClick as "TForm1.SaveAll(fileName, arMyRecord);
gives: "saverecordsunit1.pas(101,38) Error: Only class class methods, class properties and class variables can be accessed in class methods "

try changing the SaveAll definition from
Code: [Select]
procedure SaveAll(const aFileName:String; var aRecords:TMyRecordArray); to
Code: [Select]
procedure SaveAll(const aFileName:String; const aRecords:array of TMyRecord);this should allow you to use any kind of array as input to the saveall procedure. Make sure that the definition is changed in both interface and implementation sections of the unit.

Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

clemmi

  • Jr. Member
  • **
  • Posts: 54
Re: save open records to disk
« Reply #10 on: March 21, 2014, 07:01:46 pm »
taazz,

Thank you for all your help. I tried various ways and I'm able to save the array of records but I have problems retrieving it in a form that I can show it in a memo. Using your code I get the ShowMessage box ok but I want to be able to manipulate it as text in a memo or ListBox.

I get this error message with the code below:
saverecordsunit1.pas(92,66) Error: Incompatible type for arg no. 1: Got "TMyRecord.ShortString", expected "QWord"

Eventually I need to be able to save/get back at least two arrays of records in a single step. But this can be done later unless there's something I should be considering at this point to make that easier to implement later.
 
I did some reading about streams in "Lazarus - The complete guide" book and the "Learn to Program Using Lazarus" by Howard Page-Clark although some it's a bit over my head at this time I understood it much better. If you can suggest other reference material I'll appreciate it.  I want to learn not just take advantage of the forum, although it has been great so far in developing this project. Modifying/adapting sample code seems to be a good way to get started

 
 
Code: [Select]
unit saveRecordsunit1;

{$mode objfpc}{$H+}

interface
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
  StdCtrls, math;

type
  { TForm1 }
    TMyRecord = record
                ID : cardinal;
                FName : string[20];
                LName : string[80];
                BDay : TDateTime;
              end;

  TMyRecordArray = array of TMyRecord;

  TForm1 = class(TForm)

  SaveSTREAM: TButton;
  GetSTREAM: TButton;
  Memo1: TMemo;
  OpenDialog1: TOpenDialog;
  SaveDialog1: TSaveDialog;

  procedure FormCreate(Sender: TObject);

  procedure SaveSTREAMClick(Sender: TObject);
  function LoadAll(const aFilename : string):TMyRecordArray;

  procedure GetSTREAMClick(Sender: TObject);
  procedure SaveAll(const aFileName: string; const arTMyRecords: array of TMyRecord);

  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;
  arTMyRecord : array[0..5] of TMyRecord;
  i : integer;

implementation

{$R *.lfm}
{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
      for i := 0 to 5 do
        begin
          arTMyRecord[i].Fname := 'Fname: F' + intTostr(i);
          arTMyRecord[i].Lname := 'Lname: L' + intTostr(i);
        end;
end;


procedure TForm1.SaveSTREAMClick(Sender: TObject);
 begin
   if Form1.Savedialog1.Execute then
          Form1.saveall(Form1.Savedialog1.Filename, arTMyRecord);
 end;

procedure TForm1.SaveAll(const aFileName: string; const arTMyRecords:array of TMyRecord);
var
   vfileS : TFileStream = nil;
   vTotal : cardinal = 0;
begin
   vFileS := TFileStream.Create(aFilename, fmOpenReadWrite or fmShareExclusive);
   try
     vTotal := SizeOf(TMyRecord)*(High(arTMyRecords)-Low(arTMyRecords));
     vFileS.Size := vTotal;
     vFileS.Seek(0,0);
     vFileS.Write(arTMyRecords[Low(arTMyRecords)], vTotal);
   finally
     vFileS.Free;
   end;
end;


procedure TForm1.GetSTREAMClick(Sender: TObject);
var
    vList : Array of TMyRecord;
    vCntr : integer;
begin
  if Form1.Opendialog1.Execute then
      vlist := Form1.loadall(Form1.Opendialog1.Filename);

      for vCntr := low(vList) to High(vList) do
        // PROBLEM HERE TO GET BACK THE RECORD AND BE ABLE TO SHOW IT IN MEMO
        BEGIN
          Form1.Memo1.lines[vCntr] := intTostr(vList[vCntr].FName) + ' -- ' +
                                      intTostr(vList[vCntr].LName) + chr(13);
        end;

        setLength(vList,0);//free the memory.
end;


function TForm1.LoadAll(const aFilename: string): TMyRecordArray;
var
   vfileL : TFileStream;
begin
   vFileL := TFileStream.Create(aFilename, fmOpenReadWrite or fmShareExclusive);
   try
     vFileL.seek(0,0);
     SetLength(Result, ceil(vFileL.Size / SizeOf(TMyRecord)));
     vFileL.Read(Result[Low(result)], vFileL.Size);
   finally
     vFileL.Free;
   end;
end;

end.

-cl

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: save open records to disk
« Reply #11 on: March 21, 2014, 07:22:27 pm »
Code: [Select]
      for vCntr := low(vList) to High(vList) do
        // PROBLEM HERE TO GET BACK THE RECORD AND BE ABLE TO SHOW IT IN MEMO
        BEGIN
          Form1.Memo1.lines[vCntr] := intTostr(vList[vCntr].FName) + ' -- ' +
                                      intTostr(vList[vCntr].LName) + chr(13);
        end;
IntToStr converts an integer to a string so it can be passed on to a memo or listbox or what ever handles only strings don't use it in anything else especially when the fields are already strings, do something along the lines of
Code: [Select]
      Memo1.lines.Clear;
      for vCntr := low(vList) to High(vList) do
        BEGIN
          Memo1.lines.add( vList[vCntr].FName + ' -- ' +
                                       vList[vCntr].LName);
        end;
also it is a bad practice to use the form1 variable inside the Tform1 methods. You are breaking the encapsulation rule and it should be avoided at all cost.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

lainz

  • Hero Member
  • *****
  • Posts: 3139
    • Lainz
Re: save open records to disk
« Reply #12 on: March 21, 2014, 09:02:39 pm »

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: save open records to disk
« Reply #13 on: March 21, 2014, 09:11:03 pm »
Records don't have RTTI.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

clemmi

  • Jr. Member
  • **
  • Posts: 54
Re: save open records to disk
« Reply #14 on: March 21, 2014, 11:27:16 pm »
taazz,
Thanks a lot! Now it works as I needed - perfect at this point.

Now I have to work out how to save two different records the same way and hopefully in one Dialog step. Any suggestions/references are more than welcome.

--------------------------------------------------
007,
Thanks for the link to the wiki page, I'm looking at it, and also to taazz for pointingout that it's for RTTI and it doesn't work for records.
-cl