Solved

Save TList Records

Posted on 2004-08-31
14
1,818 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
Comment Utility
//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
Comment Utility
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
Comment Utility
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
 

Author Comment

by:foxjax
Comment Utility
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
Comment Utility
>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
Comment Utility
thanks meikl, i appreciate any help you can offer
0
 
LVL 17

Expert Comment

by:Wim ten Brink
Comment Utility
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
What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 17

Expert Comment

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

Expert Comment

by:kretzschmar
Comment Utility
>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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
Thank you! :)
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Internet Business Fax to Email Made Easy - With eFax Corporate (http://www.enterprise.efax.com), you'll receive a dedicated online fax number, which is used the same way as a typical analog fax number. You'll receive secure faxes in your email, fr…
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…

744 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

Need Help in Real-Time?

Connect with top rated Experts

16 Experts available now in Live!

Get 1:1 Help Now