Creating a Sequential-Access Text File

C# imposes no structure on files. Thus, the concept of a "record" does not exist in C# 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 BankUIForm

The following examples demonstrate file processing in a bank-account maintenance application. These programs have similar user interfaces, so we created reusable class BankUIForm (Fig. 18.7 from the Visual Studio Form designer) to encapsulate a base-class GUI (see the screen capture in Fig. 18.7). Class BankUIForm contains four Labels and four TextBoxes. Methods ClearTextBoxes (lines 2640), SetTextBoxValues (lines 4361) and GetTextBoxValues (lines 6475) clear, set the values of and get the values of the text in the TextBoxes, respectively.

Figure 18.7. Base class for GUIs in our file-processing applications.

1 // Fig. 18.7: BankUIForm.cs 2 // A reusable Windows Form for the examples in this chapter. 3 using System; 4 using System.Windows.Forms; 5 6 public partial class BankUIForm : Form 7 { 8 protected int TextBoxCount = 4; // number of TextBoxes on Form 9 10 // enumeration constants specify TextBox indices 11 public enum TextBoxIndices 12 { 13 ACCOUNT, 14 FIRST, 15 LAST, 16 BALANCE 17 } // end enum 18 19 // parameterless constructor 20 public BankUIForm() 21 { 22 InitializeComponent(); 23 } // end constructor 24 25 // clear all TextBoxes 26 public void ClearTextBoxes() 27 { 28 // iterate through every Control on form 29 for ( int i = 0; i < Controls.Count; i++ ) 30 { 31 Control myControl = Controls[ i ]; // get control 32 33 // determine whether Control is TextBox 34 if ( myControl is TextBox ) 35 { 36 // clear Text property ( set to empty string ) 37 myControl.Text = ""; 38 } // end if 39 } // end for 40 } // end method ClearTextBoxes 41 42 // set text box values to string array values 43 public void SetTextBoxValues( string[] values ) 44 { 45 // determine whether string array has correct length 46 if ( values.Length != TextBoxCount ) 47 { 48 // throw exception if not correct length 49 throw( new ArgumentException( "There must be " + 50 ( TextBoxCount + 1 ) + " strings in the array" ) ); 51 } // end if 52 // set array values if array has correct length 53 else 54 { 55 // set array values to text box values 56 accountTextBox.Text = values[ ( int ) TextBoxIndices.ACCOUNT ]; 57 firstNameTextBox.Text = values[ ( int ) TextBoxIndices.FIRST ]; 58 lastNameTextBox.Text = values[ ( int ) TextBoxIndices.LAST ]; 59 balanceTextBox.Text = values[ ( int ) TextBoxIndices.BALANCE ]; 60 } // end else 61 } // end method SetTextBoxValues 62 63 // return text box values as string array 64 public string[] GetTextBoxValues() 65 { 66 string[] values = new string[ TextBoxCount ]; 67 68 // copy text box fields to string array 69 values[ ( int ) TextBoxIndices.ACCOUNT ] = accountTextBox.Text; 70 values[ ( int ) TextBoxIndices.FIRST ] = firstNameTextBox.Text; 71 values[ ( int ) TextBoxIndices.LAST ] = lastNameTextBox.Text; 72 values[ ( int ) TextBoxIndices.BALANCE ] = balanceTextBox.Text; 73 74 return values; 75 } // end method GetTextBoxValues 76 } // end class BankUIForm

To reuse class BankUIForm, you must compile the GUI into a DLL by creating a project of type Windows Control Library (we named it BankLibrary). This library is provided with the code for this chapter. You might need to change references to this library in our examples when you copy them to your system, since the library most likely will reside in a different location on your system.

Class Record

Figure 18.8 contains class Record that Fig. 18.9, Fig. 18.11 and Fig. 18.12 use for maintaining the information 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 BankUIForm.

Figure 18.8. Record for sequential-access file-processing applications.

(This item is displayed on pages 894 - 895 in the print version)

1 // Fig. 18.8: Record.cs 2 // Serializable class that represents a data record. 3 using System; 4 using System.Collections.Generic; 5 using System.Text; 6 7 public class Record 8 { 9 private int account; 10 private string firstName; 11 private string lastName; 12 private decimal balance; 13 14 // parameterless constructor sets members to default values 15 public Record() : this( 0, "", "", 0.0M ) 16 { 17 } // end constructor 18 19 // overloaded constructor sets members to parameter values 20 public Record( int accountValue, string firstNameValue, 21 string lastNameValue, decimal balanceValue ) 22 { 23 Account = accountValue; 24 FirstName = firstNameValue; 25 LastName = lastNameValue; 26 Balance = balanceValue; 27 } // end constructor 28 29 // property that gets and sets Account 30 public int Account 31 { 32 get 33 { 34 return account; 35 } // end get 36 set 37 { 38 account = value; 39 } // end set 40 } // end property Account 41 42 // property that gets and sets FirstName 43 public string FirstName 44 { 45 get 46 { 47 return firstName; 48 } // end get 49 set 50 { 51 firstName = value; 52 } // end set 53 } // end property FirstName 54 55 // property that gets and sets LastName 56 public string LastName 57 { 58 get 59 { 60 return lastName; 61 } // end get 62 set 63 { 64 lastName = value; 65 } // end set 66 } // end property LastName 67 68 // property that gets and sets Balance 69 public decimal Balance 70 { 71 get 72 { 73 return balance; 74 } // end get 75 set 76 { 77 balance = value; 78 } // end set 79 } // end property Balance 80 } // end class Record

Figure 18.9. Creating and writing to a sequential-access file.

(This item is displayed on pages 896 - 900 in the print version)

1 // Fig. 18.9: CreateFileForm.cs 2 // Creating a sequential-access file. 3 using System; 4 using System.Windows.Forms; 5 using System.IO; 6 using BankLibrary; 7 8 public partial class CreateFileForm : BankUIForm 9 { 10 private StreamWriter fileWriter; // writes data to text file 11 private FileStream output; // maintains connection to file 12 13 // parameterless constructor 14 public CreateFileForm() 15 { 16 InitializeComponent(); 17 } // end constructor 18 19 // event handler for Save Button 20 private void saveButton_Click( object sender, EventArgs e ) 21 { 22 // create dialog box enabling user to save file 23 SaveFileDialog fileChooser = new SaveFileDialog(); 24 DialogResult result = fileChooser.ShowDialog(); 25 string fileName; // name of file to save data 26 27 fileChooser.CheckFileExists = false; // allow user to create file 28 29 // exit event handler if user clicked "Cancel" 30 if ( result == DialogResult.Cancel ) 31 return; 32 33 fileName = fileChooser.FileName; // get specified file name 34 35 // show error if user specified invalid file 36 if ( fileName == "" || fileName == null ) 37 MessageBox.Show( "Invalid File Name", "Error", 38 MessageBoxButtons.OK, MessageBoxIcon.Error ); 39 else 40 { 41 // save file via FileStream if user specified valid file 42 try 43 { 44 // open file with write access 45 output = new FileStream( fileName, 46 FileMode.OpenOrCreate, FileAccess.Write ); 47 48 // sets file to where data is written 49 fileWriter = new StreamWriter( output ); 50 51 // disable Save button and enable Enter button 52 saveButton.Enabled = false; 53 enterButton.Enabled = true; 54 } // end try 55 // handle exception if there is a problem opening the file 56 catch ( IOException ) 57 { 58 // notify user if file does not exist 59 MessageBox.Show( "Error opening file", "Error", 60 MessageBoxButtons.OK, MessageBoxIcon.Error ); 61 } // end catch 62 } // end else 63 } // end method saveButton_Click 64 65 // handler for enterButton Click 66 private void enterButton_Click( object sender, EventArgs e ) 67 { 68 // store TextBox values string array 69 string[] values = GetTextBoxValues(); 70 71 // Record containing TextBox values to serialize 72 Record record = new Record(); 73 74 // determine whether TextBox account field is empty 75 if ( values[ ( int ) TextBoxIndices.ACCOUNT ] != "" ) 76 { 77 // store TextBox values in Record and serialize Record 78 try 79 { 80 // get account number value from TextBox 81 int accountNumber = Int32.Parse( 82 values[ ( int ) TextBoxIndices.ACCOUNT ] ); 83 84 // determine whether accountNumber is valid 85 if ( accountNumber > 0 ) 86 { 87 // store TextBox fields in Record 88 record.Account = accountNumber; 89 record.FirstName = values[ ( int ) TextBoxIndices.FIRST ]; 90 record.LastName = values[ ( int ) TextBoxIndices.LAST ]; 91 record.Balance = Decimal.Parse( 92 values[ ( int ) TextBoxIndices.BALANCE ] ); 93 94 // write Record to file, fields separated by commas 95 fileWriter.WriteLine( 96 record.Account + "," + record.FirstName + "," + 97 record.LastName + "," + record.Balance ); 98 } // end if 99 else 100 { 101 // notify user if invalid account number 102 MessageBox.Show( "Invalid Account Number", "Error", 103 MessageBoxButtons.OK, MessageBoxIcon.Error ); 104 } // end else 105 } // end try 106 // notify user if error occurs in serialization 107 catch ( IOException ) 108 { 109 MessageBox.Show( "Error Writing to File", "Error", 110 MessageBoxButtons.OK, MessageBoxIcon.Error ); 111 } // end catch 112 // notify user if error occurs regarding parameter format 113 catch ( FormatException ) 114 { 115 MessageBox.Show( "Invalid Format", "Error", 116 MessageBoxButtons.OK, MessageBoxIcon.Error ); 117 } // end catch 118 } // end if 119 120 ClearTextBoxes(); // clear TextBox values 121 } // end method enterButton_Click 122 123 // handler for exitButton Click 124 private void exitButton_Click( object sender, EventArgs e ) 125 { 126 // determine whether file exists 127 if ( output != null ) 128 { 129 try 130 { 131 fileWriter.Close(); // close StreamWriter 132 output.Close(); // close file 133 } // end try 134 // notify user of error closing file 135 catch ( IOException ) 136 { 137 MessageBox.Show( "Cannot close file", "Error", 138 MessageBoxButtons.OK, MessageBoxIcon.Error ); 139 } // end catch 140 } // end if 141 142 Application.Exit(); 143 } // end method exitButton_Click 144 } // end class CreateFileForm

(a)

(b)

(c)

(d)

(e)

(f)

(g)

(h)

Figure 18.10. Sample data for the program of Fig. 18.9.

(This item is displayed on page 902 in the print version)

Account 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

 

Figure 18.11. Reading sequential-access files.

(This item is displayed on pages 902 - 905 in the print version)

1 // Fig. 18.11: ReadSequentialAccessFileForm.cs 2 // Reading a sequential-access file. 3 using System; 4 using System.Windows.Forms; 5 using System.IO; 6 using BankLibrary; 7 8 public partial class ReadSequentialAccessFileForm : BankUIForm 9 { 10 private FileStream input; // maintains connection to a file 11 private StreamReader fileReader; // reads data from a text file 12 13 // paramterless constructor 14 public ReadSequentialAccessFileForm() 15 { 16 InitializeComponent(); 17 } // end constructor 18 19 // invoked when user clicks the Open button 20 private void openButton_Click( object sender, EventArgs e ) 21 { 22 // create dialog box enabling user to open file 23 OpenFileDialog fileChooser = new OpenFileDialog(); 24 DialogResult result = fileChooser.ShowDialog(); 25 string fileName; // name of file containing data 26 27 // exit event handler if user clicked Cancel 28 if ( result == DialogResult.Cancel ) 29 return; 30 31 fileName = fileChooser.FileName; // get specified file name 32 ClearTextBoxes(); 33 34 // show error if user specified invalid file 35 if ( fileName == "" || fileName == null ) 36 MessageBox.Show( "Invalid File Name", "Error", 37 MessageBoxButtons.OK, MessageBoxIcon.Error ); 38 else 39 { 40 // create FileStream to obtain read access to file 41 input = new FileStream( fileName, FileMode.Open, 42 FileAccess.Read ); 43 44 // set file from where data is read 45 fileReader = new StreamReader( input ); 46 47 openButton.Enabled = false; // disable Open File button 48 nextButton.Enabled = true; // enable next record button 49 } // end else 50 } // end method openButton_Click 51 52 // invoked when user clicks Next button 53 private void nextButton_Click( object sender, EventArgs e ) 54 { 55 try 56 { 57 // get next record available in file 58 string inputRecord = fileReader.ReadLine(); 59 string[] inputFields; // will store individual pieces of data 60 61 if ( inputRecord != null ) 62 { 63 inputFields = inputRecord.Split( ',' ); 64 65 Record record = new Record( 66 Convert.ToInt32( inputFields[ 0 ] ), inputFields[ 1 ], 67 inputFields[ 2 ], Convert.ToDecimal( inputFields[ 3 ] ) ); 68 69 // copy string array values to TextBox values 70 SetTextBoxValues( inputFields ); 71 } // end if 72 else 73 { 74 fileReader.Close(); // close StreamReader 75 input.Close(); // close FileStream if no Records in file 76 openButton.Enabled = true; // enable Open File button 77 nextButton.Enabled = false; // disable Next Record button 78 ClearTextBoxes(); 79 80 // notify user if no Records in file 81 MessageBox.Show( "No more records in file", "", 82 MessageBoxButtons.OK, MessageBoxIcon.Information ); 83 } // end else 84 } // end try 85 catch ( IOException ) 86 { 87 MessageBox.Show( "Error Reading from File", "Error", 88 MessageBoxButtons.OK, MessageBoxIcon.Error ); 89 } // end catch 90 } // end method nextButton_Click 91 } // end class readSequentialAccessFileForm

(a)

(b)

(c)

(d)

(e)

(f)

(g)

(h)

Figure 18.12. Credit-inquiry program.

(This item is displayed on pages 907 - 911 in the print version)

1 // Fig. 18.12: CreditInquiryForm.cs 2 // Read a file sequentially and display contents based on 3 // account type specified by user ( credit, debit or zero balances ). 4 using System; 5 using System.Windows.Forms; 6 using System.IO; 7 using BankLibrary; 8 9 public partial class CreditInquiryForm : Form 10 { 11 private FileStream input; // maintains the connection to the file 12 private StreamReader fileReader; // reads data from text file 13 14 // name of file that stores credit, debit and zero balances 15 private string fileName; 16 17 // parameterless constructor 18 public CreditInquiryForm() 19 { 20 InitializeComponent(); 21 } // end constructor 22 23 // invoked when user clicks Open File button 24 private void openButton_Click( object sender, EventArgs e ) 25 { 26 // create dialog box enabling user to open file 27 OpenFileDialog fileChooser = new OpenFileDialog(); 28 DialogResult result = fileChooser.ShowDialog(); 29 30 // exit event handler if user clicked Cancel 31 if ( result == DialogResult.Cancel ) 32 return; 33 34 fileName = fileChooser.FileName; // get name from user 35 36 // show error if user specified invalid file 37 if ( fileName == "" || fileName == null ) 38 MessageBox.Show( "Invalid File Name", "Error", 39 MessageBoxButtons.OK, MessageBoxIcon.Error ); 40 else 41 { 42 // create FileStream to obtain read access to file 43 input = new FileStream( fileName, 44 FileMode.Open, FileAccess.Read ); 45 46 // set file from where data is read 47 fileReader = new StreamReader( input ); 48 49 // enable all GUI buttons, except for Open File button 50 openButton.Enabled = false; 51 creditButton.Enabled = true; 52 debitButton.Enabled = true; 53 zeroButton.Enabled = true; 54 } // end else 55 } // end method openButton_Click 56 57 // invoked when user clicks credit balances, 58 // debit balances or zero balances button 59 private void getBalances_Click( object sender, System.EventArgs e ) 60 { 61 // convert sender explicitly to object of type button 62 Button senderButton = ( Button )sender; 63 64 // get text from clicked Button, which stores account type 65 string accountType = senderButton.Text; 66 67 // read and display file information 68 try 69 { 70 // go back to the beginning of the file 71 input.Seek( 0, SeekOrigin.Begin ); 72 73 displayTextBox.Text = "The accounts are: "; 74 75 // traverse file until end of file 76 while ( true ) 77 { 78 string[] inputFields; // will store individual pieces of data 79 Record record; // store each Record as file is read 80 decimal balance; // store each Record's balance 81 82 // get next Record available in file 83 string inputRecord = fileReader.ReadLine(); 84 85 // when at the end of file, exit method 86 if ( inputRecord == null ) 87 return; 88 89 inputFields = inputRecord.Split( ',' ); // parse input 90 91 // create Record from input 92 record = new Record( 93 Convert.ToInt32( inputFields[ 0 ] ), inputFields[ 1 ], 94 inputFields[ 2 ], Convert.ToDecimal( inputFields[ 3 ] ) ); 95 96 // store record's last field in balance 97 balance = record.Balance; 98 99 // determine whether to display balance 100 if ( ShouldDisplay( balance, accountType ) ) 101 { 102 // display record 103 string output = record.Account + " " + 104 record.FirstName + " " + record.LastName + " "; 105 106 // display balance with correct monetary format 107 output += string.Format( "{0:F}", balance ) + " "; 108 109 displayTextBox.Text += output; // copy output to screen 110 } // end if 111 } // end while 112 } // end try 113 // handle exception when file cannot be read 114 catch ( IOException ) 115 { 116 MessageBox.Show( "Cannot Read File", "Error", 117 MessageBoxButtons.OK, MessageBoxIcon.Error ); 118 } // end catch 119 } // end method getBalances_Click 120 121 // determine whether to display given record 122 private bool ShouldDisplay( decimal balance, string accountType ) 123 { 124 if ( balance > 0 ) 125 { 126 // display credit balances 127 if ( accountType == "Credit Balances" ) 128 return true; 129 } // end if 130 else if ( balance < 0 ) 131 { 132 // display debit balances 133 if ( accountType == "Debit Balances" ) 134 return true; 135 } // end else if 136 else // balance == 0 137 { 138 // display zero balances 139 if ( accountType == "Zero Balances" ) 140 return true; 141 } // end else 142 143 return false; 144 } // end method ShouldDisplay 145 146 // invoked when user clicks Done button 147 private void doneButton_Click( object sender, EventArgs e ) 148 { 149 // determine whether file exists 150 if ( input != null ) 151 { 152 // close file and StreamReader 153 try 154 { 155 input.Close(); 156 fileReader.Close(); 157 } // end try 158 // handle exception if FileStream does not exist 159 catch( IOException ) 160 { 161 // notify user of error closing file 162 MessageBox.Show( "Cannot close file", "Error", 163 MessageBoxButtons.OK, MessageBoxIcon.Error ); 164 } // end catch 165 } // end if 166 167 Application.Exit(); 168 } // end method doneButton_Click 169 } // end class CreditInquiryForm

(a)

(b)

(c)

(d)

(e)

Class Record contains private instance variables account, firstName, lastName and balance (lines 912), which collectively represent all the information for a record. The parameterless constructor (lines 1517) sets these members by calling the four-argument constructor with 0 for the account number, empty strings ("") for the first and last name and 0.0M for the balance. The four-argument constructor (lines 2027) sets these members to the specified parameter values. Class Record also provides properties Account (lines 3040), FirstName (lines 4353), LastName (lines 5666) and Balance (lines 6979) for accessing each record's account number, first name, last name and balance, respectively.

Using a Character Stream to Create an Output File

Class CreateFileForm (Fig. 18.9) uses instances of class Record to create a sequential-access file that might be used in an accounts receivable systemi.e., a 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 the user could enter the records in any order.

Class CreateFileForm either creates or opens a file (depending on whether one exists), then allows the user to write records to that file. The using directive in line 6 enables us to use the classes of the BankLibrary namespace; this namespace contains class BankUIForm, from which class CreateFileForm inherits (line 8). Class CreateFileForm's GUI enhances that of class BankUIForm with buttons Save As, Enter and Exit.

When the user clicks the Save As button, the program invokes the event handler saveButton_Click (lines 2063). Line 23 instantiates an object of class SaveFileDialog (namespace System.Windows.Forms). Objects of this class are used for selecting files (see the second screen in Fig. 18.9). Line 24 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 24). Line 30 tests whether the user clicked Cancel by comparing this value to DialogResult.Cancel. If the values are equal, method saveButton_Click returns (line 31). Otherwise, line 33 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 4546 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 46) indicates that the FileStream object should open the file if the file exists or 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 indicates that the program can perform only 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. Line 56 catches an IOException if there is a problem opening the file or creating the StreamWriter. If so, the program displays an error message (lines 5960). If no exception occurs, the file is open for writing.

Good Programming Practice 18 1

When opening files, use the FileAccess enumeration to control user access to these files.

Common Programming Error 18 1

Failure to open a file before attempting to reference it in a program is a logic error.

After the user types information in each TextBox, the user clicks the Enter button, which calls event handler enterButton_Click (lines 66121) 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 8892 store the TextBox values in an object of type Record (created at line 72). 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 113117 handles such exceptions by notifying the user (via a MessageBox) of the improper format.

If the user entered valid data, lines 9597 write the record to the file by invoking method WriteLine of the StreamWriter object that was created at line 49. Method WriteLine writes a sequence of characters to a file. The StreamWriter object 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.

When the user clicks the Exit button, event handler exitButton_Click (lines 124143) exits the application. Line 131 closes the StreamWriter, and line 132 closes the FileStream, then line 142 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 actually open the file in any text editor to see its contents.

Reading Data from a Sequential Access Text File

Категории