[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1972
  • Last Modified:

Save TList Records

Anyone able to help in streaming the contents of a TList of records to a file and reloading them again?
0
foxjax
Asked:
foxjax
  • 5
  • 5
  • 4
2 Solutions
 
kretzschmarCommented:
//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
 
foxjaxAuthor Commented:
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
 
kretzschmarCommented:
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
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
foxjaxAuthor Commented:
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
 
kretzschmarCommented:
>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
 
foxjaxAuthor Commented:
thanks meikl, i appreciate any help you can offer
0
 
Wim ten BrinkCommented:
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
 
Wim ten BrinkCommented:
Maikl ?
Meikl I meant. Sorry about that... :P
0
 
kretzschmarCommented:
>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
 
foxjaxAuthor Commented:
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
 
Wim ten BrinkCommented:
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
 
kretzschmarCommented:
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
 
foxjaxAuthor Commented:
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
 
Wim ten BrinkCommented:
Thank you! :)
0

Featured Post

Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 5
  • 5
  • 4
Tackle projects and never again get stuck behind a technical roadblock.
Join Now