Link to home
Start Free TrialLog in
Avatar of pin_plunder
pin_plunder

asked on

Record problem

I'm working with a typed file which stores records of:

type TFileRec = packed record
  FileName: string[20];
  Path: string[70];
end;

my question is so easy it scares me. I've seen many ways to append records, and navigate through them (go to next, go to last, etc.), now HOW CAN I DELETE ONE OF THESE RECORDS?

please use TFileStream to do the deletion.

THANKS! in advance
Avatar of DragonSlayer
DragonSlayer
Flag of Malaysia image

The easiest way would be to locate which record needs to be deleted (e.g. Record 40), then stream out everything from Record 40 onwards out to another variable (another stream or another buffer). Then truncate your current file stream at Record 39, and append the data again from your buffer.
If I understand your question correctly what you need is an old simple link list
In your records you need a pointers to the prior record and pointer to the next record.
If you need to delete a record somewhere in the middle than

1)     First store the value of the pointers for the next record and prior record, of the one you need to delete.
2)     Free the memory that the pointer points to
3)     Set the pointer value of the prior records “next record pointer” to equal the saved value of the next record.
4)     Set the pointer value of the next records “prior record pointer” to equal the saved value of the prior record

Good luck Bill :-)
Avatar of robert_marquardt
robert_marquardt

He speaks of a file.

Welcome to the wonderful world of ISAM files.
The usual trick is the one memory management uses.
Keep a list (of offsets) of the allcated and a list of the free records in your file.
Deleting means moving the offset from the allocated list to the free list. Allocating goes the other way round.
If you run out of free records then add some to the record file and add their offsets to the free list.
Avatar of kretzschmar
from my paq

i've here a simple sample for a recordbased flatfile,
maybe you can adapt somewhat

unit r_file_u;

interface

uses
 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
 StdCtrls;

type
 TForm1 = class(TForm)
   BInsert: TButton;
   BUpdate: TButton;
   Edit1: TEdit;
   Edit2: TEdit;
   BPrev: TButton;
   BNext: TButton;
   BDelete: TButton;
   procedure FormCreate(Sender: TObject);
   procedure BInsertClick(Sender: TObject);
   procedure BUpdateClick(Sender: TObject);
   procedure BDeleteClick(Sender: TObject);
   procedure BNextClick(Sender: TObject);
   procedure BPrevClick(Sender: TObject);
   procedure FormClose(Sender: TObject; var Action: TCloseAction);
 private
   { Private-Deklarationen }
 public
   { Public-Deklarationen }
 end;

var
 Form1: TForm1;

Type TheRec = Record  //a record
               Name1 : String[100];
               Name2 : String[100];
             end;
Var
 f : file of TheRec;    // file of this record  
 r : TheRec;   {Buffer} // a memorybuffer for this record
 cr : LongInt; {FileCursor}  //Points to the current record in the file
 rc : LongInt; {RecordCount} //Holds the amount of records in the file

implementation

{$R *.DFM}

//open the file
procedure TForm1.FormCreate(Sender: TObject);
begin
 assignfile(f,'MyFile.Dat');  //we will work with this file
 if not(fileexists('MyFile.Dat')) then  //if this file not exists
   rewrite(f)  //create this file
 else
   reset(f);   //open this file
 cr := 0;      //
 rc := fileSize(f);
end;

procedure TForm1.BInsertClick(Sender: TObject);
begin
 r.name1 := edit1.Text;
 r.name2 := edit2.Text;
 seek(f,rc);
 write(f,r);
 inc(rc);
 cr := rc;
end;

procedure TForm1.BUpdateClick(Sender: TObject);
begin
 if cr > 0 then
 begin
   r.name1 := edit1.text;
   r.name2 := edit2.text;
   seek(f,cr-1);
   write(f,r);
 end;
end;

procedure TForm1.BDeleteClick(Sender: TObject);
begin
 if rc > 0 then      {Are there record in}
 begin
   if cr <> rc then  {CurrentCursor is not LastRecord }
   begin
     seek(f,rc-1);   {Goto LastRecord}
     read(f,r);      {Buffer LastRecord}
     seek(f,cr-1);   {Goto Record to deleted}
     write(f,r);     {Paste over LastRecord}
   end
   else              {The Record to delete is LastRecord}
   begin
     if rc > 1 then    {More then one Record}
     begin
       seek(f,rc - 2); {Goto the Record before LastRecord}
       read(f,r);      {Read for Output}
       cr := rc - 1;   {set CurrentCursor to Record before LastRecord}
     end
     else
     begin             {Last Record will be deleted : File then empty }
       r.Name1 := '';
       r.Name2 := '';
       cr := 0;
     end;
   end;
   seek(f,rc-1);     {Goto LastRecord}
   truncate(f);      {Truncate the file here}
   edit1.text := r.name1;  {Output }
   edit2.text := r.Name2;
   dec(rc);
 end;
end;

procedure TForm1.BNextClick(Sender: TObject);
begin
 if cr < rc then
 begin
   read(f,r);
   inc(cr);
   edit1.text := r.name1;
   edit2.text := r.Name2;
 end;
end;

procedure TForm1.BPrevClick(Sender: TObject);
begin
 if cr > 1 then
 begin
   seek(f,cr-2);
   read(f,r);
   dec(cr);
   edit1.text := r.name1;
   edit2.text := r.Name2;
 end;

end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 closefile(f);
end;

end.

meikl ;-)
HI,
Create MemoryStream, read all the records of your FileStream
from the beginning to the preceding to the record to be deleted, then read all the records beginning right after the
record to be deleted to the end of the FileStream, save the MemoryStream to the disk.
Sincerely,
Nestorua.
Avatar of pin_plunder

ASKER

dear kretzschmar your BDeleteClick procedure doesn't really delete the record, it just fills it with blank strings, which I also thought would work, but I'm afraid to say it doesn't, at least if the record is one in the middle of the list.

DragonSlayer, that's exactly what I was trying to do. But I get a fatal error.


var
  tempstr: string;
begin
  FRecStream.Seek(SizeOf(TFileRec) * (cmbFileName.ItemIndex + 1), soFromBeginning);
  FRecStream.Read(tempstr, FRecStream.Size - FRecStream.Position); // this line gets the error
  FRecStream.Seek(SizeOf(TFileRec) * cmbFileName.ItemIndex, soFromBeginning);
  FRecStream.Write(tempstr, SizeOf(TFileRec) * (cmbFileName.ItemIndex + 1));
end;

{ Note: FRecStream is a global variable of type TRecordStream, a class I made which descends from TFileStream, what I'm trying to do is to add a DeleteRec method which gets RecNumber as parameter and deletes that record }

The problem here is with tempstr (I think) because of it's size. I'm I right? I would like a complete example of how to do this deletion DragonSlayer, if it's not too much trouble for you. THANKS :)

if tempstr is shortstring everything "works", but not the way I like it, because shortstrings end when they found the end of line character. Is there any way to make this work using tempstr of type string.

thanks all for your comments

pablo.
then your last record is an empty record,

->
review the code you will see that the last record is copied over the to delete record, then the file will be truncated before the last record

meikl ;-)
Here's the full code in a working programme:

unit uMain;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls, ActnList;

type
  TFileRec = packed record
    FileName: string[20];
    Path: string[70];
  end;

  TfrmStreamTest = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    edtFileName: TEdit;
    edtPath: TEdit;
    ListView1: TListView;
    Button1: TButton;
    Button2: TButton;
    ActionList1: TActionList;
    actAppend: TAction;
    actDelete: TAction;
    procedure actDeleteUpdate(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure actDeleteExecute(Sender: TObject);
    procedure actAppendUpdate(Sender: TObject);
    procedure actAppendExecute(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private declarations }
    FRecStream: TFileStream;

    procedure RefreshListView;
    procedure RemoveEntry(AIndex: Integer);
  public
    { Public declarations }
  end;

var
  frmStreamTest: TfrmStreamTest;

implementation

const
  sFileName = 'Recs.dat';
var
  sPath: string;

{$R *.DFM}

procedure TfrmStreamTest.actDeleteUpdate(Sender: TObject);
begin
  actDelete.Enabled := ListView1.Selected <> nil;
end;

procedure TfrmStreamTest.FormCreate(Sender: TObject);
begin
  sPath := ExtractFilePath(Application.ExeName);
  try
    FRecStream := TFileStream.Create(sPath + sFileName,
      fmOpenReadWrite or fmShareCompat);
  except
    // on error, create a new file
    FRecStream := TFileStream.Create(sPath + sFileName,
      fmCreate or fmShareCompat);
  end;
end;

procedure TfrmStreamTest.FormDestroy(Sender: TObject);
begin
  FRecStream.Free;
end;

procedure TfrmStreamTest.RefreshListView;
var
  ListItem: TListItem;
  i, RecCount: Integer;
  FileRec: TFileRec;
begin
  ListView1.Items.Clear;
  if FRecStream.Size > 0 then
  begin
    if (FRecStream.Size mod SizeOf(TFileRec)) <> 0 then
    begin
      MessageDlg('Corrupted file!',
        mtError, [mbOk], 0);
      Exit;
    end;
    RecCount := FRecStream.Size div SizeOf(TFileRec);
    FRecStream.Seek(0, soFromBeginning);
    for i := 0 to RecCount - 1 do
    begin
      FRecStream.Read(FileRec, SizeOf(FileRec));
      ListItem := ListView1.Items.Add;
      ListItem.Caption := FileRec.FileName;
      ListItem.SubItems.Add(FileRec.Path);
    end;
  end;
end;



procedure TfrmStreamTest.actDeleteExecute(Sender: TObject);
begin
  if MessageDlg('Remove entry "' + ListView1.Selected.Caption + '"?',
    mtConfirmation, [mbYes, mbNo], 0) = mrNo then
    Exit;
  RemoveEntry(ListView1.Selected.Index);
  RefreshListView;
end;

procedure TfrmStreamTest.actAppendUpdate(Sender: TObject);
begin
  actAppend.Enabled := (Trim(edtFileName.Text) <> '') and
    (Trim(edtPath.Text) <> '');
end;

procedure TfrmStreamTest.actAppendExecute(Sender: TObject);
var
  FileRec: TFileRec;
begin
  FileRec.FileName := edtFileName.Text;
  FileRec.Path := edtPath.Text;
  FRecStream.Write(FileRec, SizeOf(FileRec));
  RefreshListView;
end;

procedure TfrmStreamTest.RemoveEntry(AIndex: Integer);
var
  sTmpMem: TMemoryStream;
  iTotalRecs: Integer;
  iRecSize: Integer;
begin
  iRecSize := SizeOf(TFileRec);
  // if it's not the last item, then we need to do swapping out
  if FRecStream.Size <> ((AIndex + 1) * iRecSize) then
  begin
    iTotalRecs := FRecStream.Size div iRecSize;
    sTmpMem := TMemoryStream.Create;
    try
      // get the record *after*
      FRecStream.Seek((AIndex + 1 )* iRecSize, soFromBeginning);
      // swap it out
      sTmpMem.CopyFrom(FRecStream, (iTotalRecs - AIndex - 1) * iRecSize);
      // write it back
      FRecStream.Seek(AIndex * iRecSize, soFromBeginning);
      FRecStream.CopyFrom(sTmpMem, 0);
    finally
      sTmpMem.Free;
    end;
  end;
  // truncate
  FRecStream.Size := FRecStream.Size - iRecSize;
end;

procedure TfrmStreamTest.FormShow(Sender: TObject);
begin
  RefreshListView;
end;

end.
ASKER CERTIFIED SOLUTION
Avatar of DragonSlayer
DragonSlayer
Flag of Malaysia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
your answer was GREAT!, thanks DragonSlayer
no probs... glad to help :)