Random Access Files

INTRODUCTION:

Welcome to this second installment on file manipulation in QuickBasic and FreeBasic. In the first installment, we covered sequential file access (Quick note, Sequential file examples should work fine in FreeBasic too). Even today I can say that sequential files have their purpose. However, when it comes to handling a bigger load of data and information, sequential files quickly become annoying as you have to load the whole file and write the whole file. It becomes quite a chore just to get the row of information you're really looking for.

This is where Random Access Files come to play. With Random Access Files you read a record and write a record, not a whole file, at a time. This is what this installment will be about. Random Access Files, how you can use them, why they are better than sequential files and how you can put them to good use in your development projects. You'll find I'll be using a rather similar structure in this tutorial as I did in the Sequential File tutor because I believe it's not a bad way to go about learning. Let's get right to it shall we?

WHAT ARE RANDOM ACCESS FILES:

To best describe what random access files are you need to understand one basic principle. All random access files are fixed length record files. This means that for one given file, all records (or rows) of information contain the same datatypes, in the same order and of the same length. The reason these are the way they are is quite simple. It allows to calculate where we are in the file and where we can go. In comparison, if you use a fixed length sequential file (like my example in the sequential file tutor) you'd know that since each row has the same format (columns of data start at the same place) then if you count the number of rows you need to read to get to the 5th record, you'd have read in X number of characters.

In sequential files, you're the one that calculates these things. In Random access files, it's already there for you, to some point. Another advantage of random access files is that when you open the file, you can open it to be both readable and writable at the same time. As you can see, this can become quite an advantage especially when dealing with larger amounts of information.

When programming for Random Access Files, I like to use User Defined Types for this purpose. Ultimately you could use independent sets of variables, calculate the length of everything and use that, but I find User Defined Types go so well hand in hand with Random Access files.

WORKING WITH RANDOM ACCESS FILES:

To work with Random Access Files, the commands are different than those used for sequential files. The OPEN statement changes as well but not by much.

The OPEN Statement works as follows:

OPEN "FILENAME.EXT" FOR RANDOM AS #FileNumber LEN = LengthOfRecord
  • "FILENAME.EXT" Is the name of the file to be opened. And can have any standard file name or path and file name.
  • #FileNumber is the standard File Handle to Assign. Here too the FREEFILE statement works great.
  • LEN = LengthOfRecord Self explanatory. The length a record should be.

To read a record from the file you need the GET command. It's syntax is as follows:

GET #FileNumber, RecordNumber, Variable
  • #FileNumber Is the filehandle number used in the OPEN statement.
  • RecordNumber is optional but allows to specify which record to read next. If it's ommited, the next available record is read in.
  • Variable is the variable in which the values of a read record get transfer to.

To write a record to a random Access File, you'll need to use the
PUT command. The syntax for the PUT command is as follows:

PUT #FileNumber, RecordNumber, Variable
  • #FileNumber Is the filehandle number used in the OPEN statement.
  • RecordNumber is optional but allows to specify which record number to write the information to in the file. If ommited, the next available record number is use. if it's at the end of the file, a new record is added.
  • Variable is the variable in which the values of a read record get transfer to.

To locate a given record, hence to move the record pointer around in the Random Access file, you'll need the SEEK statement. It's syntax is:

SEEK #FileNumber, Position
  • #FileNumber is the File Handle used in the open statement.
  • Position is the record number you wish to move to.

EXAMPLE PROGRAM

With all this theory at hand, I think now is a great time to throw in an example program to help sink things in. This example will create a random access file, insert 2 records, read them back in and display the records to the screen: This example should compile and run under FreeBasic as well.

' -------------------------------------------------------------
'  ContactInformation will hold data gotten from the data file
'  It will also allow us to write data to the file
' -------------------------------------------------------------
TYPE ContactInformation

Status AS STRING * 1

ContactID AS LONG

ContactName AS STRING * 40

PhoneNumber AS STRING * 14

Email AS STRING * 70 END TYPE

' ----------------------------------- ' Variables we'll need to work with ' ----------------------------------- DIM FileHandle AS INTEGER DIM RecordNumber AS LONG DIM Contact AS ContactInformation

' ----------------------------------------------------------- ' Get next available Handle and Open the Random Access File ' If the file doesn't exist, one is created ' ----------------------------------------------------------- FileHandle = FREEFILE OPEN "TEST.DAT" FOR RANDOM AS #FileHandle LEN = LEN(Contact)

' --------------------------------------------------- ' Assuming there are no records, we'll insert a few ' --------------------------------------------------- ' --------------------------------------------- ' Fill Information into the Contact Structure ' --------------------------------------------- Contact.Status = " " Contact.ContactID = 1 Contact.ContactName = "Stephane Richard" Contact.PhoneNumber = "(123) 323-3344" Contact.Email = "srichard@adaworld.com" ' -------------------------------------- ' Insert the record into the data file ' -------------------------------------- PUT #FileHandle, , Contact ' --------------------------------------------- ' Fill Information into the Contact Structure ' --------------------------------------------- Contact.Status = " " Contact.ContactID = 2 Contact.ContactName = "Some Other Name" Contact.PhoneNumber = "(666) 555-4444" Contact.Email = "notsure@whatever.com" ' -------------------------------------- ' Insert the record into the data file ' -------------------------------------- PUT #FileHandle, , Contact ' -------------------------------------------- ' And we'll now read and display the records ' -------------------------------------------- RecordNumber = 1 GET #FileHandle, RecordNumber, Contact PRINT Contact.ContactID; " "; Contact.ContactName; " "; Contact.PhoneNumber ' ----------------------------------------------------------- ' If no record number is specified, the next one is read in ' ----------------------------------------------------------- GET #FileHandle, , Contact PRINT Contact.ContactID; " "; Contact.ContactName; " "; Contact.PhoneNumber ' ------------------------------------- ' Of course, we close the file handle ' ------------------------------------- CLOSE #FileHandle

NOTES:

I've commented this example program and I think it's a pretty clear example However A few notes are in order.

  • I use the FREEFILE statement here to, just like in sequential files it plays the same role, gets the next available file handle, and as I said before, it really is good practice, for Random access files or another other file you'll open with the OPEN statement.
  • Like I mentionned above, I use a User Defined Type called ContactInformation for this example, as you can see, because a Random Access File's record length must be fixed, a User Defined Type really help keep the code clear. It's the main reason why I Used them. Of course, of you only have one field, then a simple variable of the data type you need will suffice.
  • For this first example, I created only 2 records (to keep the code short and clear) and to quickly show the typical usage of Random access files. As you've surely noticed, I didn't use the SEEK statement yet because I only had 2 records.
  • Right now, the Status Field in our ContactInformation User Defined Type has a space as it's value. Since there's no direct method for deleting a record per se, this Status Field will contain "D" when marked for deletion. This is a widely used method for simulating the deletion of records. DBase and other popular databases use a method similar to this one for their own deletion of records.
  • When physically deleting records in a random access file, a full rewrite of the file is in order. Basically, you copy the file, open it, browse through it saving record by record to a new file skipping the records marked for deletion. Then renaming the file to the standard filename it should have deleting the original file first, of course. Needless to say this is usually something you'll want to do only when necessary (as in when you know you have a lot of records that are marked for deletion and you know speed would be improved if such an operation was performed on the data file.

A MORE ADVANCED AND COMPLETE EXAMPLE:

The next example coming up will go a few steps further. It will Allow to modify a given record, and to add new records as well. For the sake of a more complete example, I will split this example up in 2 seperate programs. The record manager itself which will allow to add, modify and delete records. and the second part will be a sample program that will create some data so we have a couple records more to work with. I will also include a routine that will pack the data file (hence do the physical deletion of records marked for delete) so you'll also have an example of that as well.

You need to keep in mind a few things as you dwell deeper into File Manipulation techniques. First off there's a certain order of things that need to be specified in order to work properly with Random Access Files. The first of these order of things is of course:

  • You open the file
  • You read and/or write data to the file
  • You close the file

The next order is about modifying and/or deleting records.

  • You open the file
  • You position yourself on the record that needs changing
  • You load the record into your variable structure
  • You edit the different variables.
  • You reposition yourself on the record you're editing
  • You save the information to the file (or mark the record for deletion)
  • You close the file.

This next one is for adding a record (always at the end of the file)

  • You open the file
  • You create an empty data structure to be edited.
  • You edit the data that is to be saved
  • You determine the end of the file
  • You write your record
  • You close the file

NOTE: Many issues have risen in the past about how often to open a file versus how long to open the file. This is especially true for a file that may be shared by more than one use. However to me, in regular circumstances I just open the file at the beginning and close it at the end. At least for the sake of this tutorial. But if you keep variables handy to know where you should be in the file, you could just open it when you need to get a record from the file.

This second and last example of this tutorial is a much more complete application compared to the first example, it's much longer as well. It's a contact management program designed to show you all aspects of Random Access File management. I put more effort in this one to design something that's functional. It's main flaw is no data validation on data entry so just cooperate with what it's asking. It also has no error management as it's not the goal of this example. It covers everything you can do to a Random Access File in what I like to call a good application design where everything can be quickly changed to accomodate different types of structures. Data statements are used to position fields on the screen, this way fields can be added if needed. I put a special effort in documenting this source code too so that everything is as clear as it can.

' ========================================================================
'         NAME: Contacts Pro
'    FILE NAME: RAFEx2.bas
' ---------------------------------------------------------------------------
'       AUTHOR: Stephane Richard
'      LICENSE: Public domain
'    COPYRIGHT: Copyright (c) 2004 - Stephane Richard
'   DISCLAIMER: You are free to use, distribute, copy in any way you wish.
' ---------------------------------------------------------------------------
'  DESCRIPTION: This example program is the 2nd in the Random Access File
'               tutorial written by the same author.  It illustrates all
'               functions described in the tutorial in a small but complete
'               random access file management application.
' ========================================================================

' ------------------------------- ' Sub and Function Declarations ' ------------------------------- DECLARE SUB DrawMainScreen () DECLARE SUB MainMenu () DECLARE SUB AddAContact () DECLARE SUB UpdateAContact () DECLARE SUB DeleteAContact () DECLARE SUB ListAllContacts () DECLARE SUB PackContactFile () DECLARE SUB ClearViewPrint () DECLARE SUB DisplayEmptyFields () DECLARE SUB DisplayContactFields () DECLARE SUB MoveToNext () DECLARE SUB MoveToPrevious ()

' ---------------------- ' Constant Definitions ' ---------------------- CONST TRUE = -1 CONST FALSE = NOT TRUE

CONST ADDCONTACT = 0 CONST UPDATECONTACT = 1 CONST DELETECONTACT = 2 CONST LOOKUPCONTACT = 3 CONST LISTCONTACTS = 4 CONST PACKDATAFILE = 5 CONST EXITCONTACTS = 6

' ------------------------------------ ' FieldInformation User defined type ' ------------------------------------ TYPE Fieldinformation

FieldNumber AS LONG

FieldName AS STRING * 12

YPosition AS INTEGER

XPosition AS INTEGER

FieldLength AS INTEGER END TYPE

' ------------------------------------------------ ' User Defined Type used for Record Manipulation ' ------------------------------------------------ TYPE ContactInformation

Status AS STRING * 1 ' Record Status Field "D" -Marked Deleted

ContactID AS LONG

CompanyName AS STRING * 50

ContactName AS STRING * 40

Address1 AS STRING * 60

Address2 AS STRING * 60

City AS STRING * 40

State AS STRING * 40

ZipCode AS STRING * 10

HomePhone AS STRING * 14

WorkPhone AS STRING * 14

CellPhone AS STRING * 14

PagerNumber AS STRING * 14

Email AS STRING * 60

Website AS STRING * 60 END TYPE

' ----------------------------------- ' Variables we'll need to work with ' ----------------------------------- DIM SHARED FileHandle AS INTEGER DIM SHARED RecordCount AS LONG DIM SHARED RecordNumber AS LONG DIM SHARED ContactIDCounter AS LONG DIM SHARED Contact AS ContactInformation DIM SHARED FieldInfo(1 TO 14) AS Fieldinformation DIM Counter AS INTEGER

' ------------------------------------------------- ' Read field information from the data statements ' ------------------------------------------------- FOR Counter = 1 TO 14

READ FieldInfo(Counter).FieldNumber

READ FieldInfo(Counter).FieldName

READ FieldInfo(Counter).YPosition

READ FieldInfo(Counter).XPosition

READ FieldInfo(Counter).FieldLength NEXT Counter

' ------------------------------------------------------ ' We get the next FileHandle and we open the data file ' ------------------------------------------------------ FileHandle = FREEFILE OPEN "CONTACTS.DAT" FOR RANDOM AS #FileHandle LEN = LEN(Contact) RecordNumber = 1 RecordCount = LOF(FileHandle) / LEN(Contact)

' ---------------------- ' Draw the main screen ' ---------------------- WIDTH 80, 25 COLOR 14, 1 CALL DrawMainScreen IF RecordCount > 0 THEN

SEEK #FileHandle, RecordNumber

CALL DisplayContactFields ELSE

CALL DisplayEmptyFields END IF

' ------------------------------ ' Present the menu to the user ' ------------------------------ CALL MainMenu COLOR 7, 0 CLS CLOSE #FileHandle

' ----------------------------------------------------------- ' DATA STATEMENTS FOR FIELD POSITION AND LENGTH INFORMATION ' STRUCTURE: FieldNumber, "Field Name", Row, Col, Length ' ----------------------------------------------------------- DATA 1, "ContactID", 6, 71, 8 DATA 2, "CompanyName", 8, 29, 50 DATA 3, "ContactName", 9, 39, 40 DATA 4, "Address1", 11, 19, 60 DATA 5, "Address2", 12, 19, 60 DATA 6, "City", 13, 39, 40 DATA 7, "State", 14, 39, 40 DATA 8, "ZipCode", 15, 69, 10 DATA 9, "HomePhone", 17, 65, 14 DATA 10, "WorkPhone", 18, 65, 14 DATA 11, "CellPhone", 19, 65, 14 DATA 12, "PagerNumber", 20, 65, 14 DATA 13, "Email", 22, 19, 60 DATA 14, "Website", 23, 19, 60
' ========================================== ' NAME: AddAContact() ' PARAMETERS: None ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' --------------------------------------------- ' DESCRIPTION: This Subroutine requests the ' information from the user ' about the contact and adds it ' to the end of the file. ' ========================================== SUB AddAContact

DIM WorkContact AS ContactInformation

DIM WorkString AS STRING

' -------------------------------------

' First Step - Empty Fields on screen

' -------------------------------------

CALL DisplayEmptyFields

' --------------------------------------

' Next we start the data entry process

' --------------------------------------

LOCATE FieldInfo(1).YPosition, FieldInfo(1).XPosition: COLOR 14, 7

INPUT Contact.ContactID

LOCATE FieldInfo(1).YPosition, FieldInfo(1).XPosition: COLOR 14, 3

PRINT USING "######"; Contact.ContactID

LOCATE FieldInfo(2).YPosition, FieldInfo(2).XPosition: COLOR 14, 7

INPUT Contact.CompanyName

LOCATE FieldInfo(2).YPosition, FieldInfo(2).XPosition: COLOR 14, 3

PRINT Contact.CompanyName

LOCATE FieldInfo(3).YPosition, FieldInfo(3).XPosition: COLOR 14, 7

INPUT Contact.ContactName

LOCATE FieldInfo(3).YPosition, FieldInfo(3).XPosition: COLOR 14, 3

PRINT Contact.ContactName

LOCATE FieldInfo(4).YPosition, FieldInfo(4).XPosition: COLOR 14, 7

INPUT Contact.Address1

LOCATE FieldInfo(4).YPosition, FieldInfo(4).XPosition: COLOR 14, 3

PRINT Contact.Address1

LOCATE FieldInfo(5).YPosition, FieldInfo(5).XPosition: COLOR 14, 7

INPUT Contact.Address2

LOCATE FieldInfo(5).YPosition, FieldInfo(5).XPosition: COLOR 14, 3

PRINT Contact.Address2

LOCATE FieldInfo(6).YPosition, FieldInfo(6).XPosition: COLOR 14, 7

INPUT Contact.City

LOCATE FieldInfo(6).YPosition, FieldInfo(6).XPosition: COLOR 14, 3

PRINT Contact.City

LOCATE FieldInfo(7).YPosition, FieldInfo(7).XPosition: COLOR 14, 7

INPUT Contact.State

LOCATE FieldInfo(7).YPosition, FieldInfo(7).XPosition: COLOR 14, 3

PRINT Contact.State

LOCATE FieldInfo(8).YPosition, FieldInfo(8).XPosition: COLOR 14, 7

INPUT Contact.ZipCode

LOCATE FieldInfo(8).YPosition, FieldInfo(8).XPosition: COLOR 14, 3

PRINT Contact.ZipCode

LOCATE FieldInfo(9).YPosition, FieldInfo(9).XPosition: COLOR 14, 7

INPUT Contact.HomePhone

LOCATE FieldInfo(9).YPosition, FieldInfo(9).XPosition: COLOR 14, 3

PRINT Contact.HomePhone

LOCATE FieldInfo(10).YPosition, FieldInfo(10).XPosition: COLOR 14, 7

INPUT Contact.WorkPhone

LOCATE FieldInfo(10).YPosition, FieldInfo(10).XPosition: COLOR 14, 3

PRINT Contact.WorkPhone

LOCATE FieldInfo(11).YPosition, FieldInfo(11).XPosition: COLOR 14, 7

INPUT Contact.CellPhone

LOCATE FieldInfo(11).YPosition, FieldInfo(11).XPosition: COLOR 14, 3

PRINT Contact.CellPhone

LOCATE FieldInfo(12).YPosition, FieldInfo(12).XPosition: COLOR 14, 7

INPUT Contact.PagerNumber

LOCATE FieldInfo(12).YPosition, FieldInfo(12).XPosition: COLOR 14, 3

PRINT Contact.PagerNumber

LOCATE FieldInfo(13).YPosition, FieldInfo(13).XPosition: COLOR 14, 7

INPUT Contact.Email

LOCATE FieldInfo(13).YPosition, FieldInfo(13).XPosition: COLOR 14, 3

PRINT Contact.Email

LOCATE FieldInfo(14).YPosition, FieldInfo(14).XPosition: COLOR 14, 7

INPUT Contact.Website

LOCATE FieldInfo(14).YPosition, FieldInfo(14).XPosition: COLOR 14, 3

PRINT Contact.Website

' -----------------------------------

' We add the record in the datafile

' ----------------------------------- ' SEEK #FileHandle, RecordCount

PUT #FileHandle, RecordCount + 1, Contact

' -------------------------------------

' Update RecordCount and RecordNumber

' -------------------------------------

RecordNumber = RecordCount + 1

RecordCount = LOF(FileHandle) / LEN(Contact)

SEEK #FileHandle, RecordCount

WorkString = "RECORD: " + STR$(RecordNumber) + " OF " + STR$(RecordCount)

LOCATE 4, 79 - LEN(WorkString): COLOR 7, 1: PRINT WorkString

END SUB
' ============================================== ' NAME: DeleteAContact() ' PARAMETERS: None ' RETURNS: Integer for the selected menu ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' ------------------------------------------------- ' DESCRIPTION: This Ask for confirmation to ' delete and awaits the user's input ' Then Marks the current contact for ' deletion. ' ============================================== SUB DeleteAContact

DIM WorkString AS STRING

LOCATE 4, 2: COLOR 0, 4: PRINT SPACE$(78);

LOCATE 4, 2: COLOR 14, 4: INPUT "Are you sure you want to delete this contact? (Y/N) :", Confirm$

IF UCASE$(Confirm$) = "Y" THEN

Contact.Status = "D"

PUT #FileHandle, RecordNumber, Contact

END IF

LOCATE 4, 1: COLOR 7, 1: PRINT CHR$(186); SPACE$(78); CHR$(186)

LOCATE 4, 3: COLOR 7, 1: PRINT "CURRENT CONTACT INFORMATION"

WorkString = "RECORD: " + STR$(RecordNumber) + " OF " + STR$(RecordCount)

LOCATE 4, 79 - LEN(WorkString): PRINT WorkString

END SUB
' ============================================ ' NAME: DisplayContactFields() ' PARAMETERS: None ' ASSUMES: Contact Variable Filled ' CALLED FROM: MainMenu Subroutine ' ----------------------------------------------- ' DESCRIPTION: Empties the currently displayed ' fields to display the current ' contact information. Called ' whenever the record pointer in ' the data file is moved. ' ============================================ SUB DisplayContactFields

DIM WorkString AS STRING

CALL DisplayEmptyFields

IF Contact.ContactID = 0 THEN

GET #FileHandle, RecordNumber, Contact

END IF

LOCATE 4, 40

COLOR 7, 1

IF Contact.Status = "D" THEN

PRINT "DELETED"

ELSE

PRINT " "

END IF

LOCATE 4, 50: COLOR 7, 1: PRINT " "

WorkString = "RECORD: " + STR$(RecordNumber) + " OF " + STR$(RecordCount)

LOCATE 4, 79 - LEN(WorkString): PRINT WorkString

LOCATE FieldInfo(1).YPosition, FieldInfo(1).XPosition

COLOR 15, 3: PRINT Contact.ContactID

LOCATE FieldInfo(2).YPosition, FieldInfo(2).XPosition

COLOR 15, 3: PRINT Contact.CompanyName

LOCATE FieldInfo(3).YPosition, FieldInfo(3).XPosition

COLOR 15, 3: PRINT Contact.ContactName

LOCATE FieldInfo(4).YPosition, FieldInfo(4).XPosition

COLOR 15, 3: PRINT Contact.Address1

LOCATE FieldInfo(5).YPosition, FieldInfo(5).XPosition

COLOR 15, 3: PRINT Contact.Address2

LOCATE FieldInfo(6).YPosition, FieldInfo(6).XPosition

COLOR 15, 3: PRINT Contact.City

LOCATE FieldInfo(7).YPosition, FieldInfo(7).XPosition

COLOR 15, 3: PRINT Contact.State

LOCATE FieldInfo(8).YPosition, FieldInfo(8).XPosition

COLOR 15, 3: PRINT Contact.ZipCode

LOCATE FieldInfo(9).YPosition, FieldInfo(9).XPosition

COLOR 15, 3: PRINT Contact.HomePhone

LOCATE FieldInfo(10).YPosition, FieldInfo(10).XPosition

COLOR 15, 3: PRINT Contact.WorkPhone

LOCATE FieldInfo(11).YPosition, FieldInfo(11).XPosition

COLOR 15, 3: PRINT Contact.CellPhone

LOCATE FieldInfo(12).YPosition, FieldInfo(12).XPosition

COLOR 15, 3: PRINT Contact.PagerNumber

LOCATE FieldInfo(13).YPosition, FieldInfo(13).XPosition

COLOR 15, 3: PRINT Contact.Email

LOCATE FieldInfo(14).YPosition, FieldInfo(14).XPosition

COLOR 15, 3: PRINT Contact.Website

END SUB
' ================================================== ' NAME: DisplayEmptyFields() ' PARAMETERS: None ' ASSUMES: Nothing ' CALLED FROM: Main Program Section ' ----------------------------------------------------- ' DESCRIPTION: This Subroutine loops through the ' defined Field definitions to display ' the set of empty fields on the screen. ' ================================================== SUB DisplayEmptyFields

DIM Counter AS INTEGER

FOR Counter = 1 TO 14

LOCATE FieldInfo(Counter).YPosition, FieldInfo(Counter).XPosition

COLOR 0, 3

PRINT SPACE$(FieldInfo(Counter).FieldLength)

NEXT Counter

END SUB
' =========================================== ' NAME: DrawMainScreen() ' PARAMETERS: None ' ASSUMES: Nothing ' CALLED FROM: Main Section of the program ' ---------------------------------------------- ' DESCRIPTION: This subroutine clears the ' screen and draws the background ' screen of the program. ' =========================================== SUB DrawMainScreen

DIM Counter AS INTEGER

DIM WorkString AS STRING

CLS

LOCATE 1, 1: COLOR 0, 5: PRINT SPACE$(80);

LOCATE 1, 1: COLOR 14, 5: PRINT "Contacts Professional 1.00.000a"

LOCATE 1, 49: COLOR 15, 5: PRINT "Random Access File Demonstration"

LOCATE 2, 1: COLOR 0, 7: PRINT SPACE$(80);

LOCATE 3, 1: COLOR 7, 1: PRINT CHR$(201); STRING$(78, CHR$(205)); CHR$(187)

LOCATE 4, 1: COLOR 7, 1: PRINT CHR$(186); SPACE$(78); CHR$(186)

LOCATE 4, 3: COLOR 7, 1: PRINT "CURRENT CONTACT INFORMATION"

WorkString = "RECORD: " + STR$(RecordNumber) + " OF " + STR$(RecordCount)

LOCATE 4, 79 - LEN(WorkString): PRINT WorkString

LOCATE 5, 1: COLOR 7, 1: PRINT CHR$(204); STRING$(78, CHR$(205)); CHR$(185)

FOR Counter = 6 TO 23

LOCATE Counter, 1: COLOR 7, 1: PRINT CHR$(186); SPACE$(78); CHR$(186)

NEXT Counter

LOCATE 7, 1: COLOR 7, 1: PRINT CHR$(199); STRING$(78, CHR$(196)); CHR$(182)

LOCATE 10, 1: COLOR 7, 1: PRINT CHR$(199); STRING$(78, CHR$(196)); CHR$(182)

LOCATE 16, 1: COLOR 7, 1: PRINT CHR$(199); STRING$(78, CHR$(196)); CHR$(182)

LOCATE 21, 1: COLOR 7, 1: PRINT CHR$(199); STRING$(78, CHR$(196)); CHR$(182)

' -------------------

' Draw field labels

' -------------------

LOCATE 6, 3: COLOR 7, 1: PRINT "Contact ID....:"

LOCATE 8, 3: COLOR 7, 1: PRINT "Company Name..:"

LOCATE 9, 3: COLOR 7, 1: PRINT "Contact Name..:"

LOCATE 11, 3: COLOR 7, 1: PRINT "Address One...:"

LOCATE 12, 3: COLOR 7, 1: PRINT "Address Two...:"

LOCATE 13, 3: COLOR 7, 1: PRINT "City..........:"

LOCATE 14, 3: COLOR 7, 1: PRINT "State.........:"

LOCATE 15, 3: COLOR 7, 1: PRINT "Zip Code......:"

LOCATE 17, 3: COLOR 7, 1: PRINT "Home Phone....:"

LOCATE 18, 3: COLOR 7, 1: PRINT "Work Phone....:"

LOCATE 19, 3: COLOR 7, 1: PRINT "Cell Phone....:"

LOCATE 20, 3: COLOR 7, 1: PRINT "Pager Number..:"

LOCATE 22, 3: COLOR 7, 1: PRINT "Email Address.:"

LOCATE 23, 3: COLOR 7, 1: PRINT "Website URL...:"

LOCATE 24, 1: COLOR 7, 1: PRINT CHR$(200); STRING$(78, CHR$(205)); CHR$(188);

LOCATE 25, 1: COLOR 0, 5: PRINT SPACE$(80);

LOCATE 25, 1: COLOR 7, 5: PRINT " <"; CHR$(17) + CHR$(16) + "=Menu><PgUp=Prev><PgDn=Next><Ins=New><Del=Delete><Enter=Edit>";

END SUB

SUB GetContactFields

END SUB
' ========================================== ' NAME: ListAllContacts() ' PARAMETERS: None ' RETURNS: Integer for the selected menu ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' --------------------------------------------- ' DESCRIPTION: This subroutine goes about ' listing all the contact data ' that is currently present in ' the data file, one after the ' other. ' ========================================== SUB ListAllContacts

' -----------------------------------------------

' Work Variables we'll need to perform the list

' -----------------------------------------------

DIM WorkContact AS ContactInformation

DIM WorkRecord AS LONG

DIM WorkKey AS STRING

' ------------------------------------------------------

' Perform the list of contacts only if we have records

' ------------------------------------------------------

IF RecordCount > 0 THEN

CLS

' ----------------------------------------

' Position ourselves on the first record

' ----------------------------------------

SEEK #FileHandle, 1

' ------------------------------------------------

' Loop through the records and print record data

' ------------------------------------------------

DO WHILE NOT EOF(FileHandle)

GET #FileHandle, , WorkContact

PRINT WorkContact.ContactID; " "; WorkContact.ContactName; " "; WorkContact.HomePhone

LOOP

' ---------------------

' Await User KeyPress

' ---------------------

PRINT "Press Any Key To Go Back To Contact Screen."

DO WHILE WorkKey = ""

WorkKey = INKEY$

LOOP

' ------------------------------------

' Go back to the main contact screen

' ------------------------------------

CALL DrawMainScreen

SEEK #FileHandle, RecordNumber

CALL DisplayContactFields

END IF

END SUB
' ========================================== ' NAME: MainMenu() ' PARAMETERS: None ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' --------------------------------------------- ' DESCRIPTION: This Displays a main menu ' and awaits the user's input ' Calls the proper subroutine ' depending on the selected ' menu option. ' ========================================== SUB MainMenu

' ---------------------------------------

' Local variables used in this function

' ---------------------------------------

DIM Menus(6) AS STRING

DIM Counter AS INTEGER

DIM Current AS INTEGER

DIM Keyboard AS STRING

' ------------------------------------------

' Assign the Menu Items to the Menus Array

' ------------------------------------------

Menus(0) = "New"

Menus(1) = "Edit"

Menus(2) = "Delete"

Menus(3) = "List"

Menus(4) = "Pack Data"

Menus(5) = "Exit"

Current = 0

DO WHILE TRUE

' ------------------------

' Display the menu items

' ------------------------

LOCATE 2, 16

FOR Counter = 0 TO 5

IF Counter = Current THEN

COLOR 7, 0

ELSE

COLOR 0, 7

END IF

PRINT Menus(Counter); : COLOR 0, 7: PRINT " ";

NEXT Counter

' --------------------------------------------------

' Handle the keyboard to allow for menu selection

' --------------------------------------------------

Keyboard = INKEY$

SELECT CASE Keyboard

CASE CHR$(0) + CHR$(75) ' <- Left Arrow

Current = Current - 1

IF Current < 0 THEN

Current = 5

END IF

CASE CHR$(0) + CHR$(77) ' <- Right Arrow

Current = Current + 1

IF Current > 5 THEN

Current = 0

END IF

CASE CHR$(0) + CHR$(73) ' <- Page Up

CALL MoveToPrevious

CASE CHR$(0) + CHR$(81) ' <- Page Down

CALL MoveToNext

CASE CHR$(13)

' ---------------------------------------------

' Call appropriate subroutine based on choice

' ---------------------------------------------

SELECT CASE Current

CASE 0

CALL AddAContact

CASE 1

CALL UpdateAContact

CASE 2

CALL DeleteAContact

CASE 3

CALL ListAllContacts

CASE 4

CALL PackContactFile

CASE 5

CanExit = TRUE

EXIT DO

END SELECT

END SELECT

LOOP

END SUB
' ============================================== ' NAME: MoveToNext() ' PARAMETERS: None ' ASSUMES: DataFile for #FileHandle Opened ' CALLED FROM: MainMenu Subroutine ' ------------------------------------------------- ' DESCRIPTION: Adds 1 to the current RecordNumber ' and adjust to the end of file if ' needed, then loads Contact Info ' from the current record and calls ' DisplayContactFields to show the ' loaded contact information. ' ============================================== SUB MoveToNext

RecordNumber = RecordNumber + 1

IF RecordNumber > RecordCount THEN

RecordNumber = RecordCount

END IF

GET #FileHandle, RecordNumber, Contact

CALL DisplayContactFields

END SUB
' ============================================== ' NAME: MoveToPrevious() ' PARAMETERS: None ' ASSUMES: DataFile for #FileHandle Opened ' CALLED FROM: MainMenu Subroutine ' ------------------------------------------------- ' DESCRIPTION: takes 1 from RecordNumber variable ' and adjust to the start of file if ' needed, then loads Contact Info ' from the current record and calls ' DisplayContactFields to show the ' loaded contact i`nformation. ' ============================================== SUB MoveToPrevious

RecordNumber = RecordNumber - 1

IF RecordNumber < 1 THEN

RecordNumber = 1

END IF

GET #FileHandle, RecordNumber, Contact

CALL DisplayContactFields

END SUB
' ========================================== ' NAME: PackContactFile() ' PARAMETERS: None ' RETURNS: Integer for the selected menu ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' --------------------------------------------- ' DESCRIPTION: This subroutine performs the ' physical deletion of records ' marked for deletion. To do so ' it will create a temporary ' file, copy all records not ' marked for Deletion to the new ' file, once done, it will erase ' the data file, and rename the ' temporary file to the official ' data file name. ' ========================================== SUB PackContactFile

' ---------------------------

' Work Variables we'll need

' ---------------------------

DIM WorkContact AS ContactInformation

DIM Counter AS LONG

DIM SourceHandle AS INTEGER

DIM DestinationHandle AS INTEGER

' -----------------------------------------

' First we get handles and open the files

' -----------------------------------------

CLOSE #FileHandle

SourceHandle = FREEFILE

OPEN "CONTACTS.DAT" FOR RANDOM AS #SourceHandle LEN = LEN(WorkContact)

DestinationHandle = FREEFILE

OPEN "TEMP.DAT" FOR RANDOM AS #DestinationHandle LEN = LEN(WorkContact)

Counter = 0

' ----------------------------------------------------

' Main loop to do the copying of non deleted records

' ----------------------------------------------------

DO WHILE NOT EOF(SourceHandle)

GET #SourceHandle, , WorkContact

IF WorkContact.Status <> "D" THEN

Counter = Counter + 1

' -----------------------------------

' We add the record in the datafile

' -----------------------------------

SEEK #DestinationHandle, Counter

PUT #DestinationHandle, Counter + 1, Contact

END IF

LOOP

' ------------------------

' Close the opened files

' ------------------------

CLOSE #DestinationHandle

CLOSE #SourceHandle

' ----------------------------------------------------------

' Delete contact file and rename temp file to CONTACTS.DAT

' ----------------------------------------------------------

KILL "CONTACTS.DAT"

NAME "TEMP.DAT" AS "CONTACTS.DAT"

OPEN "CONTACTS.DAT" FOR RANDOM AS #FileHandle LEN = LEN(WorkContact)

END SUB
' ============================================= ' NAME: UpdateAContact() ' PARAMETERS: None ' RETURNS: Integer for the selected menu ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' ------------------------------------------------ ' DESCRIPTION: This sub start the data entry for ' fields and after saves the ' information to the file. ' ============================================= SUB UpdateAContact

DIM WorkContact AS ContactInformation

' ----------------------------------------

' First we start the data entry process

' Work from a temp stucture so if there

' are changes they are updated and shown

' ----------------------------------------

WorkContact = Contact

LOCATE FieldInfo(1).YPosition, FieldInfo(1).XPosition: COLOR 14, 7

INPUT WorkContact.ContactID

LOCATE FieldInfo(1).YPosition, FieldInfo(1).XPosition: COLOR 14, 3

PRINT USING "######"; Contact.ContactID

LOCATE FieldInfo(2).YPosition, FieldInfo(2).XPosition: COLOR 14, 7

INPUT WorkContact.CompanyName

IF (Contact.CompanyName <> WorkContact.CompanyName) AND (WorkContact.CompanyName <> "") THEN

Contact.CompanyName = WorkContact.CompanyName

END IF

LOCATE FieldInfo(2).YPosition, FieldInfo(2).XPosition: COLOR 14, 3

PRINT Contact.CompanyName

LOCATE FieldInfo(3).YPosition, FieldInfo(3).XPosition: COLOR 14, 7

INPUT WorkContact.ContactName

IF (Contact.ContactName <> WorkContact.ContactName) AND (WorkContact.ContactName <> "") THEN

Contact.ContactName = WorkContact.ContactName

END IF

LOCATE FieldInfo(3).YPosition, FieldInfo(3).XPosition: COLOR 14, 3

PRINT Contact.ContactName

LOCATE FieldInfo(4).YPosition, FieldInfo(4).XPosition: COLOR 14, 7

INPUT Contact.Address1

IF (Contact.Address1 <> WorkContact.Address1) AND (WorkContact.Address1 <> "") THEN

Contact.Address1 = WorkContact.Address1

END IF

LOCATE FieldInfo(4).YPosition, FieldInfo(4).XPosition: COLOR 14, 3

PRINT Contact.Address1

LOCATE FieldInfo(5).YPosition, FieldInfo(5).XPosition: COLOR 14, 7

INPUT Contact.Address2

IF (Contact.Address2 <> WorkContact.Address2) AND (WorkContact.Address2 <> "") THEN

Contact.Address2 = WorkContact.Address2

END IF

LOCATE FieldInfo(5).YPosition, FieldInfo(5).XPosition: COLOR 14, 3

PRINT Contact.Address2

LOCATE FieldInfo(6).YPosition, FieldInfo(6).XPosition: COLOR 14, 7

INPUT Contact.City

IF (Contact.City <> WorkContact.City) AND (WorkContact.City <> "") THEN

Contact.City = WorkContact.City

END IF

LOCATE FieldInfo(6).YPosition, FieldInfo(6).XPosition: COLOR 14, 3

PRINT Contact.City

LOCATE FieldInfo(7).YPosition, FieldInfo(7).XPosition: COLOR 14, 7

INPUT Contact.State

IF (Contact.State <> WorkContact.State) AND (WorkContact.State <> "") THEN

Contact.State = WorkContact.State

END IF

LOCATE FieldInfo(7).YPosition, FieldInfo(7).XPosition: COLOR 14, 3

PRINT Contact.State

LOCATE FieldInfo(8).YPosition, FieldInfo(8).XPosition: COLOR 14, 7

INPUT Contact.ZipCode

IF (Contact.ZipCode <> WorkContact.ZipCode) AND (WorkContact.ZipCode <> "") THEN

Contact.ZipCode = WorkContact.ZipCode

END IF

LOCATE FieldInfo(8).YPosition, FieldInfo(8).XPosition: COLOR 14, 3

PRINT Contact.ZipCode

LOCATE FieldInfo(9).YPosition, FieldInfo(9).XPosition: COLOR 14, 7

INPUT Contact.HomePhone

IF (Contact.HomePhone <> WorkContact.HomePhone) AND (WorkContact.HomePhone <> "") THEN

Contact.HomePhone = WorkContact.HomePhone

END IF

LOCATE FieldInfo(9).YPosition, FieldInfo(9).XPosition: COLOR 14, 3

PRINT Contact.HomePhone

LOCATE FieldInfo(10).YPosition, FieldInfo(10).XPosition: COLOR 14, 7

INPUT Contact.WorkPhone

IF (Contact.WorkPhone <> WorkContact.WorkPhone) AND (WorkContact.WorkPhone <> "") THEN

Contact.WorkPhone = WorkContact.WorkPhone

END IF

LOCATE FieldInfo(10).YPosition, FieldInfo(10).XPosition: COLOR 14, 3

PRINT Contact.WorkPhone

LOCATE FieldInfo(11).YPosition, FieldInfo(11).XPosition: COLOR 14, 7

INPUT Contact.CellPhone

IF (Contact.CellPhone <> WorkContact.CellPhone) AND (WorkContact.CellPhone <> "") THEN

Contact.CellPhone = WorkContact.CellPhone

END IF

LOCATE FieldInfo(11).YPosition, FieldInfo(11).XPosition: COLOR 14, 3

PRINT Contact.CellPhone

LOCATE FieldInfo(12).YPosition, FieldInfo(12).XPosition: COLOR 14, 7

INPUT Contact.PagerNumber

IF (Contact.PagerNumber <> WorkContact.PagerNumber) AND (WorkContact.PagerNumber <> "") THEN

Contact.PagerNumber = WorkContact.PagerNumber

END IF

LOCATE FieldInfo(12).YPosition, FieldInfo(12).XPosition: COLOR 14, 3

PRINT Contact.PagerNumber

LOCATE FieldInfo(13).YPosition, FieldInfo(13).XPosition: COLOR 14, 7

INPUT Contact.Email

IF (Contact.Email <> WorkContact.Email) AND (WorkContact.Email <> "") THEN

Contact.Email = WorkContact.Email

END IF

LOCATE FieldInfo(13).YPosition, FieldInfo(13).XPosition: COLOR 14, 3

PRINT Contact.Email

LOCATE FieldInfo(14).YPosition, FieldInfo(14).XPosition: COLOR 14, 7

INPUT Contact.Website

IF (Contact.Website <> WorkContact.Website) AND (WorkContact.Website <> "") THEN

Contact.Website = WorkContact.Website

END IF

LOCATE FieldInfo(14).YPosition, FieldInfo(14).XPosition: COLOR 14, 3

PRINT Contact.Website

' ------------------------------------

' We save the record in the datafile

' ------------------------------------

PUT #FileHandle, RecordNumber, Contact

' -------------------------------------

' Update RecordCount and RecordNumber

' -------------------------------------

SEEK #FileHandle, RecordNumber

END SUB

NOTES:

  • To have this 2nd example work in FreeBasic, you should change the keyboard values that I check (in the MainMenu subroutine). Right now keys are compare with CHR$(0) + CHR$(75) for left arrow. All CHR$(0) should be changed to CHR$(255) to work properly in FreeBasic.
  • I've split up all functionalities in documented (commented) subs for the sake of clarity and for the sake of being able to look at one functionality at a time and because I downright hate Gotos. On the good side this is good practice to stay away from the goto statement as much as you can.
  • I use FreeFile to get the file handles. Yes here too. Hopefully between my sequential file and this random access file you'll understand that FreeFile is not evil at all.
  • I close the file at the end. Again this is good practice even if technically ending a program is supposed to close all opened handles. I just don't trust it enough to omit from closing the file myself.
  • The list all contact options is very rudimentary in such that it simply clears the screen and list all contacts. Reporting is not the object of this tutorial, it just does the job.
  • As mentionned above, the Pack command will create a temporary file, open the temp file and the data file to perform the actual physical deletion of the records marked for deletion. Then it deletes the datafile, renames the temp file to the datafile's original name and exits.

IN CONCLUSION:

And there you have it. Hopefully, at the end of this tutorial, you'll have a good enough understanding of how Random Access Files work. A good enough understanding so you can "almost fluently" use it in your own programming projects. I used contact information in my examples, remember they could just as easily be highscore tables, list of characters (if your project has a character builder) or whatever else can be consider as tabular data or data that can be saved in a user defined type.

I hope you've enjoyed reading this tutorial at least as much as I've enjoyed writing it for you.

Stephane Richards

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-Share Alike 2.5 License.