As a local or client/server database application development tool, Delphi is hard to beat. The data access and data control components included in the VCL make building database applications easy. And the Borland Database Engine (BDE), which provides multi-platform support for several types of databases, makes compiling disparate information from a variety of data sources seem routine. Unfortunately, this simplicity comes at a price: You can't distribute a database application without the BDE, which means adding no less than two diskettes to your installation set. While you may not consider this too big a price to pay, for simple database applications (i.e., data-entry and retrieval), the BDE may be overkill. So what other alternatives do you have besides using the BDE? One answer that comes to mind is storing data in a file of records.
Building database applications with files of records
Let me start out with a sort of caveat: Constructing from scratch even the simplest database application based on a file of records is not a trivial matter. Complications arise because all the elements you're used to having at hand when using data access components just don't exist. For example, the posting and navigation rules that we take for granted as built into Tables (like what happens if the user moves beyond the end of a table), you must write into the program.
Furthermore, you can't insert or delete a record in the middle of a file; it's one more thing you have to program. Sounds pretty dismal, right? And I'm willing to bet that since those items I mentioned aren't present, most programmers won't dive into writing applications around files of records because doing so seems too difficult. That's too bad, because despite the fact that building a database from scratch isn't a trivial matter, the mechanics of building such a database aren't that difficult to master.
Another positive to building an application around a file of records is this: You're limited in what you can do with a file of records. This limitation simplifies things a great deal. Also, the mechanics behind building an application around a file of records merely involve making the right combination of calls to perform a desired action. You don't need to be a rocket scientist; you just need a bit of patience and some ingenuity. Also, if you take some time to plan out your application, you'll save yourself a lot time.
Just give it to me simple
The biggest stumbling block for most developers who are new to this type of application seems to be achieving a sense of what the program is supposed to do; that is, defining its basic functionality. Many developers erroneously start out with some sort of feature set they want to include in the program, then build the program around that set--a valid approach if you have a foundation of basic functionality already present. However, if you don't have that luxury, then you have no choice but to take a much more elemental approach to building the application. This means establishing its basic functionality before adding the bells and whistles.
Actually, you could use the following as a rule of thumb for building any type of application: Get a handle on what your program should ultimately accomplish before you write a single line of code. For some, seeing the big picture involves intensive design meetings and reviews. For others, it's a simple statement that says, "My program does X." But no matter which route you take, establishing your goal ahead of time makes the job of achieving it that much easier. So with respect to the application we discussed in the main article, let's talk about what its basic functionality will be.
You've all seen and have probably written several data-entry and retrieval programs. And if you think about it, any simple database application revolves around three basic editing actions:
Adding new records
Deleting unwanted records
Updating existing records
In addition, an application requires a limited set of navigation actions:
Moving to the first record of a file
Moving to the last record of a file
Moving to the next record
Moving to the previous record
Searching for an existing record
That's it. Here you have the basic functionality of any simple database application. You'll notice that printing isn't in this list. I omitted it intentionally because hard-copy reporting is more an ancillary than a core function, and we're concerned at this point with core functionality. The actions we've discussed here represent that functionality.
A record for your files
A file of records is a binary file that stores records in sequential order and has a specific structure, like this:
type
MyRecord = record
ID : Integer;
First,
Last : String;
end;
If you've used arrays of records in the past, think of a file of records as a persistent array of records, very much akin to a simple database. Working with a file of records in Delphi is like working with any DOS file: You assign a file to an appropriate file variable, open the file with the appropriate File Open function, perform your manipulations, then close the file. However, to successfully work with files of records, you need to take certain characteristics into account:
A file of records is like a persistent array, although unlike a Pascal array, a file of records can grow (though shrinking one down is another matter entirely, one which we'll cover below).
A file of records is a binary file, meaning that you can't easily view the files with a text editor. Therefore, all editing must take place in a program that can read the file.
Just as you do with an array variable representing a record structure, you must always declare the variable assigned to a file of records as file of .
The Read and Write functions operate much
like ReadLn and WriteLn for text files, which increment the file pointer to the next line; they always increment the pointer to the next record. This operation is in sharp contrast to the Read and Write functions for a text file, which will read or write a line of text but won't increment the file pointer.
Each record in a file of records is implicitly assigned a record number, starting with 0 (like an array). Using the Seek function, you can go directly to a specific position within the file.
File access: Opening and closing files of records
Just as with text files, you open a file of records using AssignFile, along with the Reset procedure to open up an existing file or the Rewrite procedure to create a new file. Note that Append isn't an applicable FileOpen procedure, since that's strictly a text file function. To close a file of records, you use the CloseFile procedure--the same procedure you use with a text file. Pascal old-timers should notice that I didn't mention the standard procedures Assign and Close for opening and closing. While not necessarily obsolete, these procedure names conflict with VCL method names. In order to use them safely, you have to use dot notation and specify the System unit (e.g., System.Assign); otherwise, you're likely to get a compilation error due to making one of these calls within the context of a VCL object. Note that even the online help suggests you use AssignFile and CloseFile going forward.
Now, you can't open a file of records unless you have first defined a record structure and have an appropriate variable declaration as mentioned above. For example, if you define a record structure TAddressRec, the file variable declaration would be as follows:
var
AddrFile : File of TAddressRec;
Once that's done, it's easy to open a file:
AssignFile(AddrFile, `Address.DAT');
if FileExists(`Address.DAT') then
Reset(AddrFile)
else
Rewrite(AddrFile);
(The FileExists function is a simple utility function that I use to confirm the existence of a file.)
Where am I? Record numbers and the file pointer
Records in a file of records are implicitly numbered sequentially, starting with zero for the first record. For example, if you have a file of 10 records, the records would be numbered 0 to 9. This setup is similar to standard Pascal zero-based arrays, in which the last element is always equal to the element count minus one. When you open up a file of records, the file variable has a sort of positional indicator property associated with it called a file pointer. Initially, the file pointer is set at the beginning of the file, that is, at record 0. However, you can use the Seek function to move to any position in the file. For instance, let's say you want to move to record 51 in a file. To do that, you'd make a call similar to the following:
Seek(AddrFile, 51);
From there you could read or write a file variable
depending on the prescribed action. Each read or write operation will always position the file pointer at the record following the record that received the action. For example, if the record your program just read was record 2, immediately following the call the file pointer would be placed on record 3.
This positioning isn't much of an issue for sequential writes to a file; however, it's a serious issue for sequential reads. Were you to read the last record of a file, then immediately attempt to read the next record (which is nonexistent), you'd get a runtime error because you've attempted to read beyond the end of the file. As luck would have it, though, you can call on a useful routine named FilePos to determine your position in the file.
FilePos is a function that returns the current record in a file of records. If you're doing sequential reads in a file, you'll want to make use of this function and compare its value against the FileSize function, which returns the total count of records in the file. But since the last record in a file is always numbered as one less than the total number of records, you must always subtract one from the FileSize function to safely make a comparison between your current position and the end of the file. Below is the skeleton of a looping structure that illustrates positional
comparison:
While (FilePos(AddrFile) <= (FileSize(AddrFile) - 1))
do begin
Read(AddrFile, AddrRec);
// ...do some stuff
end;
Reading and writing records
Now that we've covered the basics of opening and closing a file, and determining and manipulating our position in a file of records, we're ready to discuss the editing functions--or more simply, reading and writing to a file. To read a record from a file of records, you simply call the Read procedure and specify file and record variables as formal parameters such as
Read(AddrFile, AddrRec);
where AddrFile is the file variable and AddrRec is a record variable. Pretty simple stuff. Similarly, to write a record into a file of records, you call the Write procedure and provide the same formal parameters as in the Read procedure:
Write(AddrFile, AddrRec);
It's possible to read several records from and write several records to a file of records at one time by overloading the record parameter. For instance, let's say you want to read three records from a file simultaneously. In that case, you'd do something like the following:
Read(AddrFile, AddrRec1, AddrRec2, AddrRec3);
One thing you must keep in mind when using these functions: The file pointer is always positioned at the next record following the call. I mentioned earlier that this is a serious consideration when you're performing sequential reads from a file of records, but it can also be serious when you're editing a record. For instance, let's say you want to read record 5 from the file, edit it, then write it back to file. The proper way to write back to the original record would be as follows:
procedure ChangeLastName(RecNum : Integer; NewValue : String);
var
recBuf : TAddressRec;
begin
Seek(AddrFile, RecNum);
Read(AddrFile, recBuf);
with recBuf do
LastName := NewValue;
{Go back to the original record, then write!}
Seek(AddrFile, RecNum);
Write(AddrFile, recBuf);
end;
As you can see, I use an extra Seek immediately before the Write to move the file pointer back to the proper position. Strangely enough, both novices and experts make this fairly common error. The most important thing to remember regarding manipulating files of records is the fate of the file pointer position in relation to a Read or a Write operation.
A quick review
We've just covered a lot of ground, so let's quickly review the various functions discussed above. Table A lists the functions and the resulting actions.
Table A: Record file functions
Function Action
AssignFile Assigns a file on disk to a file variable
Reset Opens a file for editing
Rewrite Creates a new file
CloseFile Closes a file
FileSize Returns the number of records in a file
FilePos Returns the current position of the file pointer
Seek Moves the file pointer to a specific position in a file
Read Reads a record into a record variable
Write Writes a record variable to a record in a file
Truncate Deletes the remainder of a file from a given position
As you can see, you need to know only a handful of routines in order to manipulate files of records. With this information, you can now write applications that revolve around the concepts we've explored.
Before you begin, though, notice that I made no mention of the record structure with respect to any of the functions above. That's the beauty of these routines! They're so generalized that all you have to do is supply the right type of file variable, and the functions do all the work. These functions don't care what your record structure is, so long as your file variable points to a file that's of the correct type. Pretty nifty. In any case, play around with this stuff. Once you get the hang of working with files of records, you'll probably use them more often.
Sometimes you need a go-between
You move data back and forth between the database and the user interface (i.e., read or write a record) by writing to a record variable that acts as a temporary buffer between the edit fields and the fields of a record in the database. This buffer is necessary because, unlike data-aware components, there's no such thing as a data link that will link TEdit objects to fields in a record. To accomplish this linking in my applications, I use two procedures called ReadRec and SetRecVals. The ReadRec function reads the record at the current position in the file into a global record variable named AddrRec (of type TAddressRec) and then writes the variable's fields to the appropriate TEdit components' Text properties. The SetRecVals function, on the other hand, writes the values stored in the TEdit components' Text properties to AddrRec. However, you'll use the individual write methods to write data to the database, since these methods are position-dependent.
Adding and updating records to the database
Updating a record to the database merely involves moving the data from the TEdit components to the temporary buffer variable, then writing the record variable's contents to the file. However, no such thing as a field-level update exists when you're working with a file of records. When you update a record at a particular position within the file, you're actually completely overwriting the record at that position. In fact, the mechanics for adding a new record to the end of the database and overwriting an existing record are nearly identical. The only difference is that when appending a record to the database, the program merely performs a Seek beyond the end of the file, then writes the record variable.
The problem with inserting and deleting
I mentioned earlier that it's impossible to insert or delete a record in a particular position in a file of records. It's true that Delphi offers no functions that deal with these actions directly; however, that doesn't mean you can't code for them. To insert or delete a record in a file of records, you employ a buffer to temporarily hold records that you want to write back to the file. Let's quickly go over the logic of each operation before we examine the code. To insert a new record, you follow these basic steps:
Initialize an array of records of the appropriate record type.
Get the current position of the file pointer and save it.
Transfer the contents of the TEdit components into a temporary record variable.
Seek to the beginning of the file.
Read each record from the beginning of the file up to the record just before the insert position. (If you want to make an insertion at the beginning of the file, skip this step.)
Write the record to be inserted into the file to the array.
Continue reading the file from the insert position into the array until you reach the end of the file.
Write the entire array's contents back to the file.
Deleting a record isn't quite as complex as inserting a record. With a delete, you're not concerned with the records before the deletion point--only the records following it. For this reason, the steps to accomplish a delete are very simple:
Read all the records following the deletion point into a temporary record buffer.
Truncate the file from the deletion point.
Write the buffered records back to the file from the deletion point.
Truncate is a function that will remove all the records from a file starting at the current file position, then reset the current position of the file with the end of file (EOF) marker. To use the function, you have to Seek to the record you want to delete first. This step is imperative because you may delete in the wrong place, and once you call this function, there's no undo. (I'm warning you based on direct experience. Ugh!)
Movin' on up
Navigating through a file of records (that is, moving from record to record) is actually ridiculously easy. However, it can't hurt to look at a good example of how to put to use the techniques described above. So now that we've covered the basic principles of manipulating a file of records, let's employ them in a sample application that maintains a simple address and phone number database. (If you're not accustomed to creating database applications without using a true database, see "Building Database Applications with Files of Records.")
Address pattern
To begin, create a new blank form application. Next, add the appropriate components to make the form resemble the one shown in Figure A. As shown in Figure A, you'll use standard components to implement the example application's functionality. (For the time being, you can use the default names for the different components, but go ahead and set the Caption properties to match this figure.) You'll notice that it's a simple form comprised of buttons at the top and sides for navigation and editing, respectively, and TEdit components for entering basic contact information. The section below the contact information is for searching the database on a last name.
Figure A:
To give life to the form, we'll need to add some functionality to the user interface. To do so, open the code editing window for the main form, and modify the code to match Listing A. However, you might just open up the sample application in Delphi, compile it, then run it. It'll save you time.
Listing A: MAIN.PAS
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes,
Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls, ExtCtrls;
{Address Record}
type
TAddressRec = record
First : String[20];
Last : String[20];
Add1 : String[40];
Add2 : String[40];
City : String[30];
ST : String[2];
Zip : String[10];
Phone : String[20];
Fax : String[20];
end;
{Direction Type}
type
TRecMove = (recFirst, recLast, recNext, recPrev);
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
Edit5: TEdit;
Edit6: TEdit;
Edit7: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
Label7: TLabel;
btnNew: TButton;
btnFirst: TButton;
btnLast: TButton;
btnNext: TButton;
btnPrev: TButton;
btnDelete: TButton;
btnUpdate: TButton;
Edit8: TEdit;
Label8: TLabel;
Edit9: TEdit;
Label9: TLabel;
StatusBar1: TStatusBar;
Label10: TLabel;
Edit10: TEdit;
btnFind: TButton;
btnFindNext: TButton;
Bevel1: TBevel;
btnInsert: TButton;
procedure FormCreate(Sender: TObject);
procedure btnNewClick(Sender: TObject);
procedure btnFirstClick(Sender: TObject);
procedure btnLastClick(Sender: TObject);
procedure btnNextClick(Sender: TObject);
procedure btnPrevClick(Sender: TObject);
procedure FormClose(Sender: TObject;
var Action: TCloseAction);
procedure
btnDeleteClick(Sender: TObject);
procedure
btnUpdateClick(Sender: TObject);
procedure btnFindClick(Sender: TObject);
procedure
btnFindNextClick(Sender: TObject);
procedure
btnInsertClick(Sender: TObject);
private
procedure SetRecVals;
procedure ReadRec;
procedure
EnableButtons(EnableIt : Boolean);
procedure
MoveToRec(Direction : TRecMove);
procedure LocateRec(Value : String;
FromBOF : Boolean);
procedure CreateNewRec;
procedure InsertRec;
procedure UpdateRec;
procedure DeleteRec;
public
{ Public declarations }
NewRec : Boolean;
end;
var
Form1: TForm1;
AddrFile : File of TAddressRec;
AddrRec : TAddressRec;
const
MAXRECS = 2000;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
AssignFile(AddrFile, `Address.DAT');
if FileExists(`Address.DAT') then
begin
Reset(AddrFile);
if (FileSize(AddrFile) > 0) then
begin
Seek(AddrFile, 0);
ReadRec;
NewRec := False;
end;
end
else
begin
Rewrite(AddrFile);
NewRec := True;
end;
btnNew.Enabled := True;
btnInsert.Enabled := False;
btnUpdate.Caption := `&Update';
end;
procedure TForm1.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
CloseFile(AddrFile);
end;
procedure TForm1.SetRecVals;
begin
with AddrRec do begin
First := Edit1.Text;
Last := Edit2.Text;
Add1 := Edit3.Text;
Add2 := Edit4.Text;
City := Edit5.text;
ST := Edit6.Text;
Zip := Edit7.Text;
Phone := Edit8.Text;
Fax := Edit9.Text;
end;
end;
procedure TForm1.ReadRec;
begin
Read(AddrFile, AddrRec);
with AddrRec do begin
Edit1.Text := First;
Edit2.Text := Last ;
Edit3.Text := Add1 ;
Edit4.Text := Add2 ;
Edit5.text := City ;
Edit6.Text := ST ;
Edit7.Text := Zip ;
Edit8.Text := Phone;
Edit9.Text := Fax ;
end;
Seek(AddrFile, FilePos(AddrFile) - 1);
end;
procedure
TForm1.EnableButtons(EnableIt : Boolean);
begin
btnNew.Enabled := EnableIt;
btnFirst.Enabled := EnableIt;
btnPrev.Enabled := EnableIt;
btnNext.Enabled := EnableIt;
btnLast.Enabled := EnableIt;
btnInsert.Enabled := NOT EnableIt;
end;
procedure TForm1.CreateNewRec;
var
I : Integer;
begin
if NewRec then
LockWindowUpdate(Handle);
for I := 0 to ComponentCount - 1 do
if (Components[I] is TEdit) then
TEdit(Components[I]).Clear;
LockWindowUpdate(0);
NewRec := True;
EnableButtons(False);
btnUpdate.Caption := `&Post to End';
end;
procedure TForm1.InsertRec;
var
curPos,
numRecs,
I : Integer;
RecBuf : Array[0..MAXRECS] of TAddressRec;
begin
SetRecVals;
curPos := FilePos(AddrFile);
numRecs := FileSize(AddrFile);
if FilePos(AddrFile) > 0 then begin
I := 0;
Seek(AddrFile, 0);
while FilePos(AddrFile) < curPos do begin
Read(AddrFile, RecBuf[I]);
Inc(I);
end;
end;
RecBuf[curPos] := AddrRec;
I := curPos + 1;
while NOT EOF(AddrFile) do begin
Read(AddrFile, RecBuf[I]);
Inc(I);
end;
I := 0;
Seek(AddrFile, 0);
while (I <= numRecs) do begin
Write(AddrFile, RecBuf[I]);
Inc(I);
end;
Seek(AddrFile, curPos);
ReadRec;
btnUpdate.Caption := `&Update';
EnableButtons(True);
end;
procedure TForm1.UpdateRec;
var
curPos : Integer;
begin
curPos := FilePos(AddrFile);
SetRecVals;
if NewRec then
begin
Seek(AddrFile, FileSize(AddrFile));
curPos := FileSize(AddrFile) + 1;
end;
Write(AddrFile, AddrRec);
if (FileSize(AddrFile) > 0) then
begin
Seek(AddrFile, curPos);
NewRec := False;
end;
EnableButtons(True);
btnUpdate.Caption := `&Update';
end;
procedure TForm1.DeleteRec;
var
curPos,
numRecs,
I : Integer;
RecBuf : Array[0..MAXRECS] of TAddressRec;
begin
if MessageDlg(`Are you sure you want to ` +
`delete this record?',
mtConfirmation, [mbYes, mbNo], 0) =
mrNo then Exit;
if NewRec then begin
ReadRec;
NewRec := False;
EnableButtons(True);
Exit;
end;
curPos := FilePos(AddrFile);
numRecs := FileSize(AddrFile) - curPos - 1;
if (FilePos(AddrFile) <
(FileSize(AddrFile) - 1)) then
begin
Seek(AddrFile, FilePos(AddrFile) + 1);
I := 0;
while NOT EOF(AddrFile) do begin
Read(AddrFile, RecBuf[I]);
Inc(I);
end;
Seek(AddrFile, curPos);
Truncate(AddrFile);
for I := 0 to numRecs - 1 do
Write(AddrFile, RecBuf[I]);
end
else
begin
Truncate(AddrFile);
Dec(curPos);
end;
Seek(AddrFile, curPos);
ReadRec;
end;
procedure TForm1.btnDeleteClick(Sender: TObject);
begin
DeleteRec;
end;
procedure TForm1.MoveToRec(Direction : TRecMove);
var
pos : Integer;
begin
EnableButtons(True);
pos := FilePos(AddrFile);
if (FileSize(AddrFile) = 0) then
Exit;
case Direction of
recFirst : pos := 0;
recLast : pos :=
FileSize(AddrFile) - 1;
recNext : if (FilePos(AddrFile) <
(FileSize(AddrFile) - 1)) then
pos := FilePos(AddrFile) + 1
else
Exit;
recPrev : if (FilePos(AddrFile) > 0)
then
pos := FilePos(AddrFile) - 1
else
Exit;
end;
Seek(AddrFile, pos);
ReadRec;
NewRec := False;
end;
procedure TForm1.LocateRec(Value : String;
FromBOF : Boolean);
var
curPos,
SearchPos : Integer;
Found : Boolean;
begin
curPos := FilePos(AddrFile);
if FromBOF then
SearchPos := 0
else
SearchPos := curPos + 1;
Found := False;
while (SearchPos <=
(FileSize(AddrFile) - 1)) AND
(NOT Found) do begin
Seek(AddrFile, SearchPos);
Read(AddrFile, AddrRec);
if (AddrRec.Last = Value) then
begin
Found := True;
MessageBeep(MB_OK);
Seek(AddrFile, SearchPos);
{Read the record in to the fields}
ReadRec;
end;
Inc(SearchPos)
end;
if NOT Found then
ShowMessage(`Last Name not found in file');
end;
procedure TForm1.btnNewClick(Sender: TObject);
begin
CreateNewRec;
end;
procedure TForm1.btnUpdateClick(Sender: TObject);
begin
UpdateRec;
end;
procedure TForm1.btnFirstClick(Sender: TObject);
begin
MoveToRec(recFirst);
end;
procedure TForm1.btnLastClick(Sender: TObject);
begin
MoveToRec(recLast);
end;
procedure TForm1.btnNextClick(Sender: TObject);
begin
MoveToRec(recNext);
end;
procedure TForm1.btnPrevClick(Sender: TObject);
begin
MoveToRec(recPrev);
end;
procedure TForm1.btnFindClick(Sender: TObject);
begin
if (Edit10.Text <> `') then begin
if NewRec then
btnUpdateClick(Self);
LocateRec(Edit10.Text, True);
end;
end;
procedure TForm1.btnFindNextClick(Sender: TObject);
begin
LocateRec(Edit10.Text, False);
end;
procedure TForm1.btnInsertClick(Sender: TObject);
begin
InsertRec;
end;
end.
When you finish entering the code, you'll need to assign event handlers to the appropriate components. Table B contains a list of the component names, their captions, and the corresponding event handlers.
Table B: Event handler assignments
Form1 :TForm1
Caption = `Simple Address'
OnClose = FormClose
OnCreate = FormCreate
btnNew: TButton
Caption = `&New'
OnClick = btnNewClick
btnFirst: TButton
Caption = `&First'
OnClick = btnFirstClick
btnLast: TButton
Caption = `&Last'
OnClick = btnLastClick
btnNext: TButton
Caption = `N&ext'
OnClick = btnNextClick
btnPrev: TButton
Caption = `Pre&vious'
OnClick = btnPrevClick
btnDelete: TButton
Caption = `&Delete'
OnClick = btnDeleteClick
btnUpdate: TButton
Caption = `&Post'
OnClick = btnUpdateClick
btnFind: TButton
Caption = `Find'
OnClick = btnFindClick
btnFindNext: TButton
Caption = `Find Next'
OnClick = btnFindNextClick
btnInsert: TButton
Caption = `&Insert'
OnClick = btnInsertClick
After you've entered all the event handler assignments, build and test the application. Now you have a functional address book application that doesn't require any traditional database overhead.
Why bother with files of records?
Whew! We're almost at the end...
Many of you, especially the more experienced developers, are probably wondering why you should bother with the process we present in the accompanying article. The main reason is one I mentioned in that article: Sometimes the BDE is overkill for a simple database application. Not only that, there are also distribution concerns.
Application EXEs compiled for manipulating files of records are relatively small compared to those that use Delphi database VCL components. For instance, the accompanying article's sample application compiles to a mere 195K; on the other hand, a simple database application that has a TTable, a TDataSource, and a TDBGrid as the only components on its main and only form compiles to about 420K--more than twice as much disk space. Furthermore, you must install the BDE along with the latter application if you're going to distribute it to other computers. Three disks as compared to one? I'll take the latter.
Someone played devil's advocate with me just before I wrote this article and asked, "What advantages does an application built around a file of records have over one that manipulates a memory-mapped file?" Admittedly, I didn't think about this comparison when I began writing the article, but after considering what it takes to create a file mapping, I responded with a single word: "Simplicity." Creating a map view of a file isn't that difficult, but it does require going to the Windows API. For many people, that foray may not be an alternative they want to consider, especially if the task is something simple.