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
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
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 :-)
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 :-)
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.
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.
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 ;-)
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')
if not(fileexists('MyFile.Dat
rewrite(f) //create this file
else
reset(f); //open this file
cr := 0; //
rc := fileSize(f);
end;
procedure TForm1.BInsertClick(Sender
begin
r.name1 := edit1.Text;
r.name2 := edit2.Text;
seek(f,rc);
write(f,r);
inc(rc);
cr := rc;
end;
procedure TForm1.BUpdateClick(Sender
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
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.
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.
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(TFi leRec) * (cmbFileName.ItemIndex + 1), soFromBeginning);
FRecStream.Read(tempstr, FRecStream.Size - FRecStream.Position); // this line gets the error
FRecStream.Seek(SizeOf(TFi leRec) * 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.
DragonSlayer, that's exactly what I was trying to do. But I get a fatal error.
var
tempstr: string;
begin
FRecStream.Seek(SizeOf(TFi
FRecStream.Read(tempstr, FRecStream.Size - FRecStream.Position); // this line gets the error
FRecStream.Seek(SizeOf(TFi
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 ;-)
->
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.actDeleteUp date(Sende r: TObject);
begin
actDelete.Enabled := ListView1.Selected <> nil;
end;
procedure TfrmStreamTest.FormCreate( Sender: TObject);
begin
sPath := ExtractFilePath(Applicatio n.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.RefreshList View;
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(File Rec.Path);
end;
end;
end;
procedure TfrmStreamTest.actDeleteEx ecute(Send er: TObject);
begin
if MessageDlg('Remove entry "' + ListView1.Selected.Caption + '"?',
mtConfirmation, [mbYes, mbNo], 0) = mrNo then
Exit;
RemoveEntry(ListView1.Sele cted.Index );
RefreshListView;
end;
procedure TfrmStreamTest.actAppendUp date(Sende r: TObject);
begin
actAppend.Enabled := (Trim(edtFileName.Text) <> '') and
(Trim(edtPath.Text) <> '');
end;
procedure TfrmStreamTest.actAppendEx ecute(Send er: 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(FRecStrea m, (iTotalRecs - AIndex - 1) * iRecSize);
// write it back
FRecStream.Seek(AIndex * iRecSize, soFromBeginning);
FRecStream.CopyFrom(sTmpMe m, 0);
finally
sTmpMem.Free;
end;
end;
// truncate
FRecStream.Size := FRecStream.Size - iRecSize;
end;
procedure TfrmStreamTest.FormShow(Se nder: TObject);
begin
RefreshListView;
end;
end.
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.actDeleteUp
begin
actDelete.Enabled := ListView1.Selected <> nil;
end;
procedure TfrmStreamTest.FormCreate(
begin
sPath := ExtractFilePath(Applicatio
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
begin
FRecStream.Free;
end;
procedure TfrmStreamTest.RefreshList
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(File
end;
end;
end;
procedure TfrmStreamTest.actDeleteEx
begin
if MessageDlg('Remove entry "' + ListView1.Selected.Caption
mtConfirmation, [mbYes, mbNo], 0) = mrNo then
Exit;
RemoveEntry(ListView1.Sele
RefreshListView;
end;
procedure TfrmStreamTest.actAppendUp
begin
actAppend.Enabled := (Trim(edtFileName.Text) <> '') and
(Trim(edtPath.Text) <> '');
end;
procedure TfrmStreamTest.actAppendEx
var
FileRec: TFileRec;
begin
FileRec.FileName := edtFileName.Text;
FileRec.Path := edtPath.Text;
FRecStream.Write(FileRec, SizeOf(FileRec));
RefreshListView;
end;
procedure TfrmStreamTest.RemoveEntry
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(FRecStrea
// write it back
FRecStream.Seek(AIndex * iRecSize, soFromBeginning);
FRecStream.CopyFrom(sTmpMe
finally
sTmpMem.Free;
end;
end;
// truncate
FRecStream.Size := FRecStream.Size - iRecSize;
end;
procedure TfrmStreamTest.FormShow(Se
begin
RefreshListView;
end;
end.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
your answer was GREAT!, thanks DragonSlayer
no probs... glad to help :)