Solved

Save TList Records

Posted on 2004-08-31
14
1,860 Views
Last Modified: 2010-05-18
Anyone able to help in streaming the contents of a TList of records to a file and reloading them again?
0
Comment
Question by:foxjax
  • 5
  • 5
  • 4
14 Comments
 
LVL 27

Expert Comment

by:kretzschmar
ID: 11939925
//write
var
  f : file of TYourRecord;
  i : integer;
begin
  assignfile(f,'PathFileNameHere');
  rewrite(f);
  for i := 0 to YourTList.Count - 1 do
    write(f,TYourRecord(YourTList[i]^);
  closefile(f);
end;

//load
var
  f : file of TYourRecord;
  i : integer;
  p : TYourRecord^;
begin
  assignfile(f,'PathFileNameHere');
  reset(f);
  while not eof(f) do
  begin
    new(p);
    read(f,p^);
    YourTlist.add(p);
  end;
  closefile(f);
end;

just from head

meikl ;-)
0
 

Author Comment

by:foxjax
ID: 11940007
i tried the write section and can't get it to work

my Tlist is set up like this (if it helps)

Type

  TRecord = ^TCard;

  TCard = packed record

          Item           : Integer;
          Name           : String;
          Limit          : Boolean;
          Period         : String;


end;


  Group  : TList;
0
 
LVL 27

Expert Comment

by:kretzschmar
ID: 11940040
well, there may be typos in my code above.

first of all you must limit your strings

>Name           : String;
to
Name           : String[200];  //for ex.

otherwise a pointer would be stored instead of the string itself
(on load this pointer-value is usual invalid)

if you run into compiler/runtime errors of my code above,
just tell me the errormessage (sorry no delphi on hand yet,
so i can't test myself)

meikl ;-)
                   
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:foxjax
ID: 11940086
I get "incompatible type" errors and others when i try to make the code work.

The problem is that i need the strings to be more than 255 characters in the record which is why i have not set the limit.
0
 
LVL 27

Expert Comment

by:kretzschmar
ID: 11940105
>The problem is that i need the strings to be more than 255 characters

well in this case its get a bit more complicated,
you should redefine your record into a class derived from TPersistant,
then you can use the streaming-methods

forget my code above, will provide a (tested) sample this evening

meikl ;-)
0
 

Author Comment

by:foxjax
ID: 11940132
thanks meikl, i appreciate any help you can offer
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 11940233
You need strings longer than 255 characters? Well, if you know a maximum length, you could replace your strings with an "Array[MaxLength] of Char" and make sure the last character of your string is a #0 character. Delphi supports these arrays quite well. The only drawback is that your file will become huge.

The other method is like Meikl says, using a stream. But if you only use a single recordtype in your file, it's not that difficult anyway. It's just that you have to write each field of your record separately to the stream, and for strings you'll alsi have to read/write the string length.

Something like:

procedure WriteList( List: TList );
var
  Card: PCard;
  i: integer;
  Len: Integer;
  Stream: TStream;
  List: TList;
begin
  Stream := TFileStream.Create( 'Yourfile', fmCreate );
  for I := 0 to Pred( List.Count ) do begin
    Card := List[ I ]^;
    Stream.Write( Card.Item, SizeOf( Card^.Item ) );
    Len := Length( Card^.Name );
    Stream.Write( Len, SizeOf( Len ) );
    Stream.Write( PChar( Card^.Name )^, Len );
    Stream.Write( Card^.Limit, SizeOf( Card^.Limit ) );
    Len := Length( Card^.Period );
    Stream.Write( Len, SizeOf( Len ) );
    Stream.Write( PChar( Card^.Period )^, Len );
  end;
  Stream.Free;
end;

Reading the list will be a bit more difficult, though, since you don't know the length of list on file. (Unless you write that too). But basically it's silimar to this code:

procedure ReadList( List: TList );
var
  Card: PCard;
  Len: Integer;
  Stream: TStream;
begin
  Stream := TFileStream.Create( 'Yourfile', fmOpenRead );
  while ( Stream.Position < Pred( Stream.Size ) ) do begin
    New( Card );
    List.Add( Card );
    Stream.Read( Card^.Item, SizeOf( Card^.Item ) );
    Stream.Read( Len, SizeOf( Len ) );
    SetLength( Card^.Name, Len );
    Stream.Read( PChar( Card^.Name )^, Len );
    Stream.Read( Card.Limit, SizeOf( Card^.Limit ) );
    Stream.Read( Len, SizeOf( Len ) );
    SetLength( Card^.Period, Len );
    Stream.Read( PChar( Card.Period )^, Len );
  end;
  Stream.Free;
end;

I haven't tested this code so don't sue me if it creashes. ;-) It's just to give a rough idea about how to do it. Maikl will probably provide you a nicer example, right? ;-)
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 11940236
Maikl ?
Meikl I meant. Sorry about that... :P
0
 
LVL 27

Expert Comment

by:kretzschmar
ID: 11940285
>will probably provide you a nicer example, right? ;-)

will do my best, but currently i'm handicaped by no delphi available
in my office

>Maikl ?
>Meikl I meant. Sorry about that... :P

no prob

well, alex your method is a good one, my thoughts was to go through
the delphi inbuild streaming system, thats why i have suggested to
change the record-type into an objecttype. my sample will be
based on this then.

meikl ;-)


0
 

Author Comment

by:foxjax
ID: 11940352
Thanks WorkShop_Alex

I see your point about the strings - so i don't see why i cannot limit them using array[0..<upper limit>] of char

especially if this makes things a lot easier.

0
 
LVL 17

Assisted Solution

by:Wim ten Brink
Wim ten Brink earned 100 total points
ID: 11940892
An array[0..Upperlimit] of char does have a limitation. You need to know the length of the string. You could specify an array of 1000 characters but if you store just "Hello, World." in it (12 characters) then you're wasting 99% of this memory. (And of your disk storage.) Basically, you would either have to store a length variable too, telling you that Name has a string of 12 characters, or you need one more character for the End-Of-String character, which happens to be #0. Delphi, and especially Delphi 5 and higher, can work quite well with arrays of chars and typecasting them to and from regular strings. It's just that using arrays of fixed lengths waste quite a lot of memory.

The Delphi shortstring (the string[x] type) are also just arrays of chars with element 0 containing the length of the whole string. But since these are maximum 256 bytes total, they don't waste that much of memory, compared to an array of 1000 bytes. A normal string in Delphi just takes as much bytes as the string length itself, with a few more as overhead to specify the length and location.

If you want to keep your data file small, a streamable solution is the best option since it wastes the least amount of disk space. However, if you want a fast file format that allows you to tell exactly how many records there are and allowing you to jump to any random record then a File of TCard is the better solution if you need one quickly. If you have some more time, a stream solution would be better, completed with e.g. an index-file. But then you're slowly going to writing your own database format. ;-)

Something like:
type
  PCard=^TCard;
  TCard = packed record
    Item : Integer;
    Name : Array[0..1000] of char;
    Limit : Boolean;
    Period : Array[0..1000] of char;
  end;
  TCardFile = File of TCard;

should work just fine, too.

But now another problem. If you ever move to Delphi 8 then you will not be able to use file of types anymore. It's some nastiness in the way .NET treats data types. (As objects, not as pieces of memory.) Thus if you ever want to upgrade to .NET you would have to rewrite your code.

Am I correct in assuming you're trying to convert an old Turbo Pascal program? ;-)
0
 
LVL 27

Accepted Solution

by:
kretzschmar earned 350 total points
ID: 11943568
well a small fastcoded sample:

unit RecordAsObject_u;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  contnrs, StdCtrls  //used -> TObjectList
  ;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Edit4: TEdit;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    SaveDialog1: TSaveDialog;
    OpenDialog1: TOpenDialog;
    Button5: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
  private
    RecordList : TObjectList;  //using a TObjectList instead of TList
    listIndex : integer;
  public
    { Public declarations }
  end;

  TRecordClass = Class(TComponent)  //to be more simplified, using Tcomponent
  private
    FItem : Integer;
    FName : String;
    FLimit: Integer;
    FPeriod : String;
  published
    property Item : Integer read FItem write FItem;
    property Name : String read FName write FName;
    property Limit: Integer read FLimit write FLimit;
    property Period: String read FPeriod write FPeriod;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
  RecordList := TObjectList.Create(True);
  //RecordList owns the Objects and will free it self
  ListIndex := -1;  //No Data
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  RecordList.Clear;  //free all objects
  RecordList.Free;   //FreeSelf
end;

//Adding Records
procedure TForm1.Button1Click(Sender: TObject);
var
  vRecord : TRecordClass;
begin
  vRecord := TRecordClass.Create(nil);
  try
    vRecord.Item := strtoint(edit1.text);
    vRecord.Name := edit2.text;
    vRecord.Limit := strtoint(edit3.text);
    vRecord.Period := edit4.text;
    listIndex := RecordList.Add(vRecord);
  except  //on conversion error
    vRecord.free;
    raise;
  end;
end;

//Browse forward
procedure TForm1.Button2Click(Sender: TObject);
begin
  if listindex < RecordList.Count-1 then
  begin
    inc(listIndex);
    Edit1.Text := IntToStr(TRecordClass(RecordList[listIndex]).Item);
    Edit2.Text := TRecordClass(RecordList[listIndex]).Name;
    Edit3.Text := IntToStr(TRecordClass(RecordList[listIndex]).Limit);
    Edit4.Text := TRecordClass(RecordList[listIndex]).Period;
  end
  else showmessage('Currently on last entry');
end;

//Browse backward
procedure TForm1.Button3Click(Sender: TObject);
begin
  if listindex > 0 then
  begin
    dec(listIndex);
    Edit1.Text := IntToStr(TRecordClass(RecordList[listIndex]).Item);
    Edit2.Text := TRecordClass(RecordList[listIndex]).Name;
    Edit3.Text := IntToStr(TRecordClass(RecordList[listIndex]).Limit);
    Edit4.Text := TRecordClass(RecordList[listIndex]).Period;
  end
  else showmessage('Currently on first entry');
end;

//savetofile
procedure TForm1.Button4Click(Sender: TObject);
var
  i : integer;
  fs: TfileStream;
begin
  if savedialog1.Execute then
  begin
    fs := TFileStream.Create(savedialog1.FileName,fmCreate);
    try
      for i := 0 to RecordList.Count - 1 do
        fs.WriteComponent(TRecordClass(RecordList[i]));
    finally
      fs.free;
    end;
  end;
end;


//load
procedure TForm1.Button5Click(Sender: TObject);
var
  i : integer;
  fs: TfileStream;
  vRecord : TRecordClass;
begin
  if opendialog1.Execute then
  begin
    RecordList.Clear;  //clear list
    fs := TFileStream.Create(opendialog1.FileName,fmopenRead);
    try
      while fs.Position < fs.size do
        RecordList.add(fs.ReadComponent(nil));
    finally
      fs.free;
    end;
    if RecordList.Count > 0 then
    begin
      listIndex := 0;
     Edit1.Text := IntToStr(TRecordClass(RecordList[listIndex]).Item);
     Edit2.Text := TRecordClass(RecordList[listIndex]).Name;
     Edit3.Text := IntToStr(TRecordClass(RecordList[listIndex]).Limit);
     Edit4.Text := TRecordClass(RecordList[listIndex]).Period;
    end
    else
    begin
      ShowMessage('No Data');
      ListIndex := -1;
    end;
  end;
end;

Initialization
  RegisterClass(TRecordClass);  //must be registered
end.

hope this shows an alternative

meikl ;-)
0
 

Author Comment

by:foxjax
ID: 11960305
Raised points from 250 - 450

Thanks meikl, your code will give me a good head start to learning all of this.

Workshop_Alex: i've included some points for your help in all of this too.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 11961394
Thank you! :)
0

Featured Post

Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
This video shows how to use Hyena, from SystemTools Software, to bulk import 100 user accounts from an external text file. View in 1080p for best video quality.

680 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question