18.5. Creating a Sequential-Access Text File Visual Basic imposes no structure on files. Thus, the concept of a "record" does not exist in Visual Basic files. This means that you must structure files to meet the requirements of your applications. In the next few examples, we use text and special characters to organize our own concept of a "record." Class FrmBankUI The following examples demonstrate file processing in a bank-account maintenance application. These programs have similar user interfaces, so we used the Visual Studio Form designer to create reusable base class FrmBankUI (Fig. 18.7), which encapsulates the common GUI components (see the screen capture in Fig. 18.7). Class FrmBankUI contains four Labels and four TextBoxes. Method ClearTextBoxes (lines 1727) clears the TextBoxes' contents. Method SetTextBoxValues (lines 3049) sets the text in the TextBoxes. Method GetTextBoxValues (lines 5266) gets the values from the TextBoxes. Figure 18.7. Base class for GUIs in our file-processing applications. 1 ' Fig. 18.7: FrmBankUI.vb 2 ' A reusable Windows Form for the examples in this chapter. 3 4 Public Class FrmBankUI 5 ' number of TextBoxes on Form 6 Protected TextBoxCount As Integer = 4 7 8 ' enumeration constants specify TextBox indices 9 Public Enum TextBoxIndices 10 ACCOUNT 11 FIRST 12 LAST 13 BALANCE 14 End Enum ' TextBoxIndices 15 16 ' clear all TextBoxes 17 Public Sub ClearTextBoxes() 18 ' iterate through every Control on form 19 For i As Integer = 0 To Controls.Count - 1 20 Dim myControl As Control = Controls(i) ' get control 21 ' determine whether Control is TextBox 22 If TypeOf myControl Is TextBox Then 23 ' clear Text property (set to empty string) 24 myControl.Text = "" 25 End If 26 Next i 27 End Sub ' ClearTextBoxes 28 29 ' set text box values to string array values 30 Public Sub SetTextBoxValues(ByVal values() As String) 31 ' determine whether string array has correct length 32 If values.Length <> TextBoxCount Then 33 ' throw exception if not correct length 34 Throw New ArgumentException( _ 35 "There must be " & (TextBoxCount + 1) & _ 36 " strings in the array") 37 ' set array values if array has correct length 38 Else 39 ' set array values to text box values 40 txtAccount.Text = _ 41 values(Convert.ToInt32(TextBoxIndices.ACCOUNT)) 42 txtFirstName.Text = _ 43 values(Convert.ToInt32(TextBoxIndices.FIRST)) 44 txtLastName.Text = _ 45 values(Convert.ToInt32(TextBoxIndices.LAST)) 46 txtBalance.Text = _ 47 values(Convert.ToInt32(TextBoxIndices.BALANCE)) 48 End If 49 End Sub ' SetTextBoxValues 50 51 ' return text box values as string array 52 Public Function GetTextBoxValues() As String() 53 Dim values(TextBoxCount) As String 54 55 ' copy text box fields to string array 56 values(Convert.ToInt32(TextBoxIndices.ACCOUNT)) = _ 57 txtAccount.Text 58 values(Convert.ToInt32(TextBoxIndices.FIRST)) = _ 59 txtFirstName.Text 60 values(Convert.ToInt32(TextBoxIndices.LAST)) = _ 61 txtLastName.Text 62 values(Convert.ToInt32(TextBoxIndices.BALANCE)) = _ 63 txtBalance.Text 64 65 Return values 66 End Function ' GetTextBoxValues 67 End Class ' FrmBankUI | To reuse class FrmBankUI, you must compile the GUI into a DLL (we called it BankLibrary) as described in Section 14.13. We provide the BankLibrary with the examples for this chapter. When you copy these examples to your system, you might need to delete the reference to BankLibrary and add it again, since the library most likely will reside in a different location on your system. Class Record Figure 18.8 contains class Record, which Fig. 18.9, Fig. 18.11 and Fig. 18.12 use to maintain the data in each record that is written to or read from a file. This class also belongs to the BankLibrary DLL, so it is located in the same project as class FrmBankUI. Figure 18.8. Record for sequential-access file-processing applications. 1 ' Fig. 18.8: Record.vb 2 ' Class that represents a data record. 3 Imports System.Text 4 5 Public Class Record 6 Private accountValue As Integer 7 Private firstNameValue As String 8 Private lastNameValue As String 9 Private balanceValue As Decimal 10 11 ' parameterless constructor sets members to default values 12 Public Sub New() 13 MyClass. New(0, "", "", 0D ) 14 End Sub ' New 15 16 ' overloaded constructor sets members to parameter values 17 Public Sub New(ByVal account As Integer, _ 18 ByVal firstName As String, ByVal lastName As String, _ 19 ByVal balance As Decimal) 20 21 accountValue = account 22 firstNameValue = firstName 23 lastNameValue = lastName 24 balanceValue = balance 25 End Sub ' New 26 27 ' property that gets and sets Account 28 Public Property Account() As Integer 29 Get 30 Return accountValue 31 End Get 32 Set(ByVal value As Integer ) 33 accountValue = value 34 End Set 35 End Property ' Account 36 37 ' property that gets and sets FirstName 38 Public Property FirstName() As String 39 Get 40 Return firstNameValue 41 End Get 42 Set(ByVal value As String) 43 firstNameValue = value 44 End Set 45 End Property ' FirstName 46 47 ' property that gets and sets LastName 48 Public Property LastName() As String 49 Get 50 Return lastNameValue 51 End Get 52 Set(ByVal value As String) 53 lastNameValue = value 54 End Set 55 End Property ' LastName 56 57 ' property that gets and sets Balance 58 Public Property Balance() As Decimal 59 Get 60 Return balanceValue 61 End Get 62 Set(ByVal value As Decimal ) 63 balanceValue = value 64 End Set 65 End Property ' Balance 66 End Class ' Record | Figure 18.9. Creating and writing to a sequential-access file. 1 ' Fig. 18.9: FrmCreateFile.vb 2 ' Creating a sequential-access file. 3 Imports System.IO 4 Imports BankLibrary ' imports classes from Figs. 18.7 and 18.8 5 6 Public Class FrmCreateFile 7 Private fileWriter As StreamWriter ' writes data to text file 8 Private output As FileStream ' maintains connection to file 9 10 ' event handler for Save Button 11 Private Sub btnSave_Click(ByVal sender As System.Object, _ 12 ByVal e As System.EventArgs) Handles btnSave.Click 13 ' create dialog box enabling user to save file 14 Dim fileChooser As New SaveFileDialog() 15 Dim result As DialogResult = fileChooser.ShowDialog() 16 Dim fileName As String ' name of file to save data 17 18 fileChooser.CheckFileExists = False ' allow user to create file 19 20 ' exit event handler if user clicked "Cancel" 21 If result = Windows.Forms.DialogResult.Cancel Then 22 Return 23 End If 24 25 fileName = fileChooser.FileName ' get specified file name 26 27 ' show error if user specified invalid file 28 If fileName = "" Or fileName Is Nothing Then 29 MessageBox.Show("Invalid File Name", "Error" , _ 30 MessageBoxButtons.OK, MessageBoxIcon.Error) 31 Else 32 ' save file via FileStream if user specified valid file 33 Try 34 ' open file with write access 35 output = New FileStream( _ 36 fileName, FileMode.OpenOrCreate, FileAccess.Write) 37 38 ' sets file to where data is written 39 fileWriter = New StreamWriter(output) 40 41 ' disable Save button and enable Enter button 42 btnSave.Enabled = False 43 btnEnter.Enabled = True 44 ' handle exception if there is a problem opening the file 45 Catch ex As IOException 46 ' notify user if file does not exist 47 MessageBox.Show("Error opening file", "Error", _ 48 MessageBoxButtons.OK, MessageBoxIcon.Error) 49 End Try 50 End If 51 End Sub ' btnSave_Click 52 53 ' event handler for Enter Button 54 Private Sub btnEnter_Click(ByVal sender As System.Object, _ 55 ByVal e As System.EventArgs) Handles btnEnter.Click 56 ' store TextBox values string array 57 Dim values As String() = GetTextBoxValues() 58 59 ' Record containing TextBox values to serialize 60 Dim record As New Record() 61 62 ' determine whether TextBox account field is empty 63 If values(TextBoxIndices.ACCOUNT) <> "" Then 64 ' store TextBox values in Record and serialize Record 65 Try 66 ' get account number value from TextBox 67 Dim accountNumber As Integer = _ 68 Int32.Parse(values(TextBoxIndices.ACCOUNT)) 69 70 ' determine whether accountNumber is valid 71 If accountNumber > 0 Then 72 ' store TextBox fields in Record 73 record.Account = accountNumber 74 record.FirstName = values(TextBoxIndices.FIRST) 75 record.LastName = values(TextBoxIndices.LAST) 76 record.Balance = _ 77 Decimal.Parse(values(TextBoxIndices.BALANCE)) 78 79 ' write Record to file, fields separated by commas 80 fileWriter.WriteLine( _ 81 record.Account & "," & record.FirstName & "," & _ 82 record.LastName & "," & record.Balance) 83 Else 84 ' notify user if invalid account number 85 MessageBox.Show("Invalid Account Number", "Error" , _ 86 MessageBoxButtons.OK, MessageBoxIcon.Error) 87 End If 88 ' notify user if error occurs in serialization 89 Catch ex As IOException 90 MessageBox.Show("Error Writing to File", "Error" , _ 91 MessageBoxButtons.OK, MessageBoxIcon.Error) 92 ' notify user if error occurs regarding parameter format 93 Catch ex As FormatException 94 MessageBox.Show("Invalid Format" , "Error" , _ 95 MessageBoxButtons.OK, MessageBoxIcon.Error) 96 End Try 97 End If 98 99 ClearTextBoxes() ' clear TextBox values 100 End Sub ' btnEnter_Click 101 102 ' event handler for Exit Button 103 Private Sub btnExit_Click(ByVal sender As System.Object, _ 104 ByVal e As System.EventArgs) Handles btnExit.Click 105 ' determine whether file exists 106 If output IsNot Nothing Then 107 Try 108 fileWriter.Close() ' close StreamWriter 109 output.Close() ' close file 110 ' notify user of error closing file 111 Catch ex As IOException 112 MessageBox.Show("Cannot close file", "Error", _ 113 MessageBoxButtons.OK, MessageBoxIcon.Error) 114 End Try 115 End If 116 117 Application.Exit() 118 End Sub ' btnExit_Click 119 End Class ' FrmCreateFile
(a)
(b)
(c)
(d)
(e)
(f)
(g)
(h) | Class Record contains Private instance variables accountValue, firstNameValue, lastNameValue and balanceValue (lines 69), which collectively represent all the information for a record. The parameterless constructor (lines 1214) sets these members by calling the four-argument constructor with 0 for the account number, empty strings ("") for the first and last names and 0D for the balance. The four-argument constructor (lines 1725) sets these members to the specified parameter values. Class Record also provides properties Account (lines 2835), FirstName (lines 3845), LastName (lines 4855) and Balance (lines 5865) for accessing each record's account number, first name, last name and balance, respectively. Using a Character Stream to Create an Output File Class FrmCreateFile (Fig. 18.9) uses instances of class Record to create a sequential-access file that might be used in an accounts-receivable systema program that organizes data regarding money owed by a company's credit clients. For each client, the program obtains an account number and the client's first name, last name and balance (i.e., the amount of money that the client owes to the company for previously received goods and services). The data obtained for each client constitutes a record for that client. In this application, the account number is used as the record keyfiles are created and maintained in account-number order. This program assumes that the user enters records in account-number order. However, a comprehensive accounts-receivable system would provide a sorting capability so that the user could enter the records in any order. Class FrmCreateFile either creates or opens a file (depending on whether one exists), then allows the user to write records to that file. The Imports statement in line 4 enables us to use the classes of the BankLibrary namespace; this namespace contains class FrmBankUI, from which class FrmCreateFile inherits (specified in the file FrmCreateFile.Designer.vb). Class FrmCreateFile's GUI enhances that of class FrmBankUI with buttons Save As, Enter and Exit. When the user clicks the Save As button, the program invokes the event handler btnSave_Click (lines 1151). Line 14 instantiates a SaveFileDialog object (namespace System.Windows.Forms). Objects of this class are used for selecting files (see the second screen in Fig. 18.9). Line 15 calls SaveFileDialog method ShowDialog to display the dialog. When displayed, a SaveFileDialog prevents the user from interacting with any other window in the program until the user closes the SaveFileDialog by clicking either Save or Cancel. Dialogs that behave in this manner are called modal dialogs. The user selects the appropriate drive, directory and file name, then clicks Save. Method ShowDialog returns a DialogResult specifying which button (Save or Cancel) the user clicked to close the dialog. This is assigned to DialogResult variable result (line 15). Line 21 tests whether the user clicked Cancel by comparing this value to Windows.Forms.DialogResult.Cancel. If the values are equal, method btnSave_Click returns (line 22). Otherwise, line 25 uses SaveFileDialog property FileName to obtain the user-selected file. You can open files to perform text manipulation by creating objects of class FileStream. In this example, we want the file to be opened for output, so lines 3536 create a FileStream object. The FileStream constructor that we use receives three argumentsa String containing the path and name of the file to open, a constant describing how to open the file and a constant describing the file permissions. The constant FileMode.OpenOrCreate (line 36) indicates that the FileStream object should open the file if the file exists and create the file if it does not exist. There are other FileMode constants describing how to open files; we introduce these constants as we use them in examples. The constant FileAccess.Write (from the FileAccess enumeration) indicates that the program can only perform write operations with the FileStream object. There are two other constants for the third constructor parameterFileAccess.Read for read-only access and FileAccess.ReadWrite for both read and write access. The StreamWriter object (line 39) is constructed with a FileStream argument that specifies the file to which the StreamWriter will output text. Class StreamWriter belongs to the System.IO namespace. Line 45 catches an IOException if there is a problem opening the file or creating the StreamWriter. If so, the program displays an error message (lines 4748). If no exception occurs, the file is open for writing. Common Programming Error 18.1 | Failure to open a file before attempting to reference it in a program is a logic error. |
After typing information in each TextBox, the user clicks the Enter button, which calls event handler btnEnter_Click (lines 54100) to save the data from the TextBoxes into the user-specified file. If the user entered a valid account number (i.e., an integer greater than zero), lines 7377 store the TextBox values in an object of type Record (created in line 60). If the user entered invalid data in one of the TextBoxes (such as non-numeric characters in the Balance field), the program throws a FormatException. The Catch block in lines 9395 handles such exceptions by notifying the user (via a MessageBox) of the improper format. If the user entered valid data, lines 8082 write the record to the file by invoking method WriteLine of the StreamWriter object that was created at line 39. Method WriteLine writes a sequence of characters to a file. We separate each field with a comma in this example, and we place each record on its own line in the file. When the user clicks the Exit button, event handler btnExit_Click (lines 103118) exits the application. Line 108 closes the StreamWriter, line 109 closes the FileStream, then line 117 terminates the program. Note that the call to method Close is located in a try block. Method Close throws an IOException if the file or stream cannot be closed properly. In this case, it is important to notify the user that the information in the file or stream might be corrupted. Performance Tip 18.1 | Close each file explicitly when the program no longer needs to reference the file. This can reduce resource usage in programs that continue executing long after they finish using a specific file. The practice of explicitly closing files also improves program clarity. |
Performance Tip 18.2 | Releasing resources explicitly when they are no longer needed makes them immediately available for reuse by other programs, thus improving resource utilization. |
In the sample execution for the program in Fig. 18.9, we entered information for the five accounts shown in Fig. 18.10. The program does not depict how the data records are rendered in the file. To verify that the file has been created successfully, we create a program in the next section to read and display the file. Since this is a text file, you can open it in any text editor to see its contents. Figure 18.10. Sample data for the program in Fig. 18.9Account Number | First Name | Last Name | Balance |
---|
100 | Nancy | Brown | -25.54 | 200 | Stacey | Dunn | 314.33 | 300 | Doug | Barker | 0.00 | 400 | Dave | Smith | 258.34 | 500 | Sam | Stone | 34.98 |
|