Working with Files of Records


There's a sample application to accompany this article


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 <type>.
  • 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:
fr_app.gif (5966 bytes)

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.