Objectives of the Week
Overview of File Processing
Files are needed because we need to store information on a permanent basis. The data written to disk will be available when we want to use it again, and it will be available for other programs to use as well.
There are many type of files and many different file formats. BASIC has the ability to access or create any type of file that we want. For many file formats though, the programming involved may be prohibitive, but the tool set will support the low-level I/O functions. We will concentrate on text files and on accessing them sequentially or randomly.
File processing is made available in VB through a number of functions. These functions are really remnants from when Visual Basic wasn't Visual. In generic terms, in order to process a file you must first OPEN it and when you finish up, you must CLOSE it. To store a piece of information, you must WRITE it to the file, and to retrieve the information from a file you must READ it from the file.
What does it mean to Open a File? In short, it means that you are making that file available to your program. The Open statement requests from the operating system the space that it needs to manage the file. This space is made up of a file management control block and a number of buffers that will stage the information being read or written to the disk. The Open statement will often read the first few blocks of data into the allocated buffers.
Disk I/O is very slow. The functions do a pretty good job of minimizing the PHYSICAL I/O that takes place. The cost in time for reading small blocks of data is very high, so large blocks of data are read into the buffers, and each LOGICAL I/O takes the requested information from the buffer rather than physically reading the disk. The same is true on a write instruction. The data to be written to disk is placed in a buffer and after there have been enough writes to fill the buffer, the large block in then physically written to the disk. You must always be sure to close the file when you are done as this step will "flush" the remaining buffers out to the disk. All of this is invisible to the programmer and is most evident when reading from or writing to a floppy. Often, when processing small text files from a diskette, you see the disk light go on when the file is opened, and if any data is to be written back to disk, again when then the file is closed.
One other point that I think should be emphasized is that it is ONLY at open time that you refer to a file by its PHYSICAL name. On any read, any write, and when the file is closed, the it is referred to SYMBOLICALLY using a defined variable.
Visual Basic provides four modes of processing for text files
Input | Sequential | File must exist If it does not, an error is raised |
Input |
Output | Sequential | File will be created and if a file with this name exists, it will be destroyed | Output |
Append | Sequential | If the file exists, it will be opened for output and
positioned at the end If the file does not exist, it will be created |
Output |
Random | Random | Open for both Input and Output processing If the file does not exist, it will be created |
Input and Output. Fixed size records |
FreeFile
FreeFile is a function that is built into Visual Basic and it is called to acquire a unique identifier that can be used to symbolically refer to a file. It is used in conjunction with the Open statement, and then used on every I/O operation performed on the opened file.
Dim intFN as Integer
intFN = FreeFile
I use intFN for File Number, but you can use a name that has meaning to the program you are writing. If you were processing a Student File, you may call it intStudent or maybe fnStudent.
Open
To open a file, you must provide the physical file name, that is the full file specification that consists of the Drive, the Path, the File Name and the Extension. Gaining access to the full filespec can be done in a number of ways. The full filespec is returned from the common dialog, or you may use the Drive, Directory and File ListBoxes that will be demonstrated in the first example, or you can construct the full file name using App.Path and the name of the file. The open function also needs the processing mode and the file id provided by FreeFile.
intFN = FreeFile
Open strFileName For Input As intFN
This example will open the file name stored in the string strFileName for Read Only
Close
When you are all done processing the file you must issue the close function. Close will flush any buffered information out to disk and release the resources that were allocated at open back to the operation system. Failure to close a file may result in "unpredictable results".
Close intFN
Sequential File Processing
Sequential access will be used in two different ways. First, we can process a text file as a single block of text. There will be no attempt to segment the content into individual records. There will be no delimiter to indicate a logical break in the text.The second way will allow the building and processing of data files consisting of logical records within the text. These records will generally be delimited with a Carriage Return / Line Feed combination. In both cases, the text can be supported using a text editor, such as WordPad or NotePad.
Sequential File Processing Functions
Input | Input(intBytes, intFN) | Returns all of the characters it reads, including commas, carriage returns, linefeeds, quotation marks, and leading spaces. |
LOF | LOF(intFN) | Returns the length of the file in Bytes |
When processing an entire text file, the Input and LOF functions work well together. Use the LOF function to determine the size of the text file, then pass this value to the Input function to, in effect, read the entire file.
Line Input | Line Input intFN, strIOArea | The Line Input statement reads from a file one character at a time until it encounters a CR/LF combination. The CR/LF is skipped. |
Print intFN, strIOArea | Writes display formatted data to the file and appends a CR/LF. |
Data read with Line Input is usually written to the file with Print.
EOF | EOF(intFN) | Returns a True when an attempt is made to read beyond the last record in the file |
Note that all of these functions use the symbolic reference to the file and not the physical filespec.
File Viewer Application
This example uses the Drive, Directory, and File List Boxes, along with an Input statement that reads the entire file into a TextBox. It also uses the LoadPicture function to display the content of graphic files.
Private Function GetFileText(strFileName As String) As String
Dim lngLength As Long
Dim intFN As Integer
Dim strInput As String
intFN = FreeFile
Open strFileName For Input As intFN
lngLength = LOF(intFN)
If lngLength < 32767 Then
strInput = Input(lngLength, intFN)
Else
strInput = "Sorry, but the file is too
large"
End If
Close intFN
GetFileText = strInput
End Function
The value returned from this function is stored in a TextBox with MultiLine = True and the ScrollBars set as needed.
imgPic.Picture = LoadPicture(frmFile.strFileSpec)
This example makes a point to use the frmFile form as a dialog to return the file name, much like the Common Dialog control does.
Here are a couple of functions you might find handy
Error Handling
You have without a doubt experienced the way Visual Basic handles run-time errors. VB will politely display a (somewhat) helpful error message, and then rudely terminate your program. The error handling works a little differently in the design environment as you get the option to Debug the program and VB will position you at the offending statement.
As programs become more advanced, there are certain run-time errors that are simply not avoidable and rather than let VB handle the errors (by terminating the program), you can trap the run time errors and then react to specific errors that you can anticipate with a specific action.
Coding On Error in a VB event handler will trap ANY run time error that occurs. This condition is in effect for the duration of the event handler. When a run time error occurs, control is passed to the label associated with the On Error condition. This label must be within the event handler.
On Error GoTo ErrorRoutine | Pass control to ErrorRoutine when a run time error occurs |
On Error GoTo 0 | Turn off error handling |
Resume Next | Resume execution of the program at the instruction following the statement that generated the error. |
Resume | Resume execution of the program at the instruction that generated the error. This essentially means to retry the operation. |
The Structure of an Event Handler that Contains Error Handling
It is generally easiest to implement an error handler by building an event handler that
is separated into two separate sections. The normal processing path occurs when there is
no run time error raised. When the normal processing completes, the Exit Sub instruction
will bypass the exception processing. Whenever a run time error occurs, control is
immediately passed to the label specified with the On Error statement coded at the
beginning of the Event Handler.
The Err Object The Err Object contains information related to the error condition that was detected. The Error Code and a Text Description of the error are placed in Err.Number and Err.Description. Error Numbers between 1 and 1000 are reserved for Visual Basic. The description contains the same text value that you see when your program terminated when an unhandled run time error is generated. You can check the value of Err.Number when looking to handle specific anticipated errors. VB even gives you the ability to generate your own run time errors. On the surface, this might sound like an absurd feature, but it allows you some flexibility in the design and the testing of your application. When testing, you may be aware that a certain condition can occur under a circumstance that you cannot readily recreate. You can test your error handling logic by "manually" generating the error. As a design feature, it is a good thing to spilt the event handler into a normal processing segment and an error processing segment. By allowing you to generate your own error, and force a jump to the error handler, you maintain the separate normal and error handling paths.
These features are demonstrated in these two code snippits:
Private Sub cmdDisk_Click() Dim rc As Integer On Error GoTo DiskError Dir "a:" lblMessage = "No Problem with Disk Access" Exit Sub DiskError: Select Case Err.Number Case 52 rc = MsgBox("Disk Error", _ vbAbortRetryIgnore + vbCritical, _ "Disk Error") If rc = vbRetry Then lblMessage = "Disk Error: Retry" Resume ElseIf rc = vbAbort Then lblMessage = "Disk Error: Abort" On Error GoTo 0 Resume Else lblMessage = "Disk Error: Ignore" Resume Next End If Case Else lblMessage = "Error: " & Err.Number & _ " Message: " & Err.Description End Select End Sub |
' Set the Error Handler ' Access the Disk ' Normal Path ' Exit Normally ' The Error Handler ' Looking for a Code 52 ' Give the user an option ' Let them Retry the Operation ' Abort will crash the program ' Just pretend it did not happen |
Private Sub cmdFile_Click() Dim intFN As Integer Dim lngFileSize As Long On Error GoTo FileError CD.ShowOpen intFN = FreeFile Open CD.FileName For Input As #intFN lngFileSize = LOF(intFN) If lngFileSize > 32767 Then Error 1001 End If lblMessage = "No problem with Text File" Exit Sub FileError: Select Case Err.Number Case 32755 lblMessage = "Dialog Cancelled" Case 1001 lblMessage = "Text File is Too Big for a TextBox" Case Else MsgBox Err.Number & ":" & Err.Description End Select End Sub |
' Set the Error Handler ' Show the Common Dialog ' Open the File ' Locally Generated Run Time Error ' Error Handler ' The user pressed cancel on the Dialog ' Handle the forced run time error ' Number and Description for anything else |
Error Handling should be implemented anywhere that a run time error could occur. This is sometimes difficult to anticipate so it might not be unreasonable to setup error handling in every event handler. The default action might be to display the VB generated message but it will also prevent the program from crashing.
Download - The Mechanics of Error Handling
Download - File Access Error Handling
Sequential File Processing Example
This example will process a text file sequentially. Each record read is added to a Combo Box. There is some basic error handling involved although the effect is to ignore any error associated with the file processing. In this case, if the High School Text file does not exist, the error that would be generated on Open is ignored.
The Combo Box is initialized using values from the HighSchool Text file. The initial processing takes place in Form Load. The file is opened, and as each record is read, the contents are added to the Combo Box. The file is Closed after all the records have been processed.
Private Sub Form_Load()
Dim FN As Integer
Dim IOArea As String
On Error GoTo NoFile
FN = FreeFile
Open strFileSpec(FileName) For Input As FN
Do While Not EOF(FN)
Line Input #FN, IOArea
cboSchool.AddItem IOArea
Loop
Close FN
Exit Sub
NoFile:
End Sub
When the program terminates, the contents of the Combo Box is written back to the HighSchool Text file. This process Opens the file for Output, which destroys the contents. A new file is created using the contents of the Combo Box.
Private Sub Form_Unload(Cancel As Integer)
Dim FN As Integer
Dim i As Integer
FN = FreeFile
Open strFileSpec(FileName) For Output As FN
For i = cboSchool.ListCount - 1 To 0 Step -1
Print #FN, cboSchool.List(i)
Next i
Close FN
End Sub
This method is an effective way of maintaining the Text File. It is also possible to Append a record to a file each time a new High School is added to the Combo Box.
Random Access Files
A Random Access file is a sequential file of fixed sized records. This is Record I/O as opposed to string processing which was detailed in the previous sections. The advantage of random processing is that you have the ability to jump directly to any given record in the file. Each record consists of a series of fields and is defined as a User Defined Type (UDT) with fixed sized strings. In order to process the file randomly, you must supply the length of the records when the file is opened and you must supply a value indicating which record you want to access on the read and write statements.
Define your record using a UDT | Type udtAddress aValid As String * 1 aName As String * 20 aAddress As String * 20 aCSZ As String * 30 aPhone As String * 15 End Type |
Specify the record length when the file is opened |
Open strFileSpec For Random As #intFN Len = Len(udtMyAddress) |
Get | Get intFN, intRecNumber, udtMyAddress | Retrieves the specified record from disk. The content is placed in the structure |
Put | Put intFN, intRecNumber, udtMyAddress | Writes the contents of structure to disk. The disk location is determined by the Record Number specified. |
Data read with Get has usually been written to the file with Put.
Each time an I/O operation completes, the current record position is changes to refer to the next sequential record in the file. The Record Number is optional and if it is omitted, the record will be determined by the current record position.
You can query or set the current record position using different forms of the Seek instruction.
Seek intFN, intPos | Will change the Current Record Position to the record specified |
Seek(intFN) | Will return the Current Record Position |
Because the record are fixed length, it is easy to determine how these functions locate the requested. The functions will position to a particular byte offset from within the file. This can be calculated using the formula: (n-1) * Size of Record. This formula is never used within a program, but is used to explain how the I/O functions keep track of the current position in the file.
It is also a simple matter to determine how many records are in the file: RecordCount = LOF(intFN) / Len(IOArea)
It is not possible to physically delete a record from a Random Access file without rewriting every record in the file that follows the one to be deleted. The alternative is a logical delete, which is nothing more than a rewrite of the record with an indicator set that tell the program that the record is not to be processed.
When processing at the record level, you will need to write sunbroutines to move the data from the screen to the record and from the record to the screen.
Public Sub FileToScreen() With AddressRecord frmMain.txtName.Text = .aName frmMain.txtAddr.Text = .aAddress frmMain.txtCSZ.Text = .aCSZ frmMain.txtPhone.Text = .aPhone End With End Sub |
Public Sub ScreenToFile() With AddressRecord .aName = frmMain.txtName.Text .aAddress = frmMain.txtAddr.Text .aCSZ = frmMain.txtCSZ.Text .aPhone = frmMain.txtPhone.Text End With End Sub |
The Basic I/O functions involve reading a record, writing a record, and updating a record.
The delete is a form of update that sets the Valid field to Deleted.
Public Function GetRecord(intKey
As Integer) As Boolean Dim blnFound As Boolean Get #fnAddress, intKey, AddressRecord If AddressRecord.aValid = RecValid Then blnFound = True Else blnFound = False End If GetRecord = blnFound End Function |
Public Sub WriteRecord(intKey
As Integer) Put #fnAddress, intKey, AddressRecord End Sub Private Sub cmdDelete_Click() |
The Key to Random Access
In order for Random Access files to be effectively used, it is important to have a way to map a meaningful identifier (like Name) to the unmeaningful record number. There are many solutions to this problem, but none of them are particularly easy to program. The ListBox method of displaying the key (like Name) and storing the Record Number in the associated ItemData property is effective on a small scale. The collection also assists but as the collection is a class, much of the detail of the file access can be hidden.
The example uses random access to Add, Update, and Delete records in a file. The delete is a logical delete as it is not possible to physically delete a record from a sequential file. The program also allows forward and backward browsing along with some logic to display the appropriate navigation buttons depending on the operation selected. This example also tries to simulate the features of database navigation that will be discussed next week.
Build the Example
This is the Collection Class Wrapper that contains the Random Access to a Person File.
Step by Step through the Example
It is important to note that the User Interface had almost no changes. Any change necessary to store the Person information on disk was made in the Collection Class Module.
Lab 1 -From the Text - Page 440, VB Mail Order
Step by Step through the First Lab
Let me know when you are done and I will check you off as Complete
Reading Assignment
Programming Assignment
As Always: The assignment is due before the start of class next week
Page 440, Problem 10.6
This assignment extends the assignment from Week 10
Put the Random Access File Handling in the Collection Class