2. When the app executes, another compiler (known as the just-in-time compiler
17.5 Creating a Sequential-Access Text File
C# imposesnostructure on files. Thus, the concept of a “record” doesnotexist in C# files.
This means that you must structure files to meet the requirements of your apps. The next few examples use text and special characters to organize our own concept of a “record.”
ClassBankUIForm
The following examples demonstrate file processing in a bank-account maintenance app.
These programs have similar user interfaces, so we created reusable class BankUIForm (Fig. 17.7) to encapsulate a base-class GUI (see the screen capture in Fig. 17.7). Class
BankUIForm(part of theBankLibraryproject with this chapter’s examples) contains four
Labels and fourTextBoxes. MethodsClearTextBoxes(lines 28–40),SetTextBoxValues
(lines 43–64) andGetTextBoxValues(lines 67–78) clear, set the values of and get the val- ues of the text in theTextBoxes, respectively.
1 // Fig. 17.7: BankUIForm.cs
2 // A reusable Windows Form for the examples in this chapter.
3 using System;
4 using System.Windows.Forms;
5
6 namespace BankLibrary 7 {
8 public partial class BankUIForm : Form
9 {
10 protected int TextBoxCount = 4; // number of TextBoxes on Form 11
12 // enumeration constants specify TextBox indices 13 public enum TextBoxIndices
14 {
15 ACCOUNT,
16 FIRST,
17 LAST,
18 BALANCE
19 } // end enum 20
Fig. 17.7 | Base class for GUIs in our file-processing apps. (Part 1 of 3.)
17.5 Creating a Sequential-Access Text File 683
21 // parameterless constructor 22 public BankUIForm()
23 {
24 InitializeComponent();
25 } // end constructor 26
27 // clear all TextBoxes 28 public void ClearTextBoxes()
29 {
30 // iterate through every Control on form 31 foreach ( Control guiControl in Controls )
32 {
33 // determine whether Control is TextBox 34 if ( guiControl is TextBox )
35 {
36 // clear TextBox
37 ( ( TextBox ) guiControl ).Clear();
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 ) + " strings in the array" ) );
51 } // end if
52 // set array values if array has correct length
53 else
54 {
55 // set array values to TextBox values
56 accountTextBox.Text =
57 values[ ( int ) TextBoxIndices.ACCOUNT ];
58 firstNameTextBox.Text =
59 values[ ( int ) TextBoxIndices.FIRST ];
60 lastNameTextBox.Text = values[ ( int ) TextBoxIndices.LAST ];
61 balanceTextBox.Text =
62 values[ ( int ) TextBoxIndices.BALANCE ];
63 } // end else
64 } // end method SetTextBoxValues 65
66 // return TextBox values as string array 67 public string[] GetTextBoxValues()
68 {
69 string[] values = new string[ TextBoxCount ];
70
71 // copy TextBox fields to string array
72 values[ ( int ) TextBoxIndices.ACCOUNT ] = accountTextBox.Text;
73 values[ ( int ) TextBoxIndices.FIRST ] = firstNameTextBox.Text;
Fig. 17.7 | Base class for GUIs in our file-processing apps. (Part 2 of 3.)
Usingvisual inheritance(Section 15.13), you can extend this class to create the GUIs for several examples in this chapter. Recall that to reuse classBankUIForm, you must compile the GUI into a class library, then add a reference to the new class library in each project that will reuse it. This library (BankLibrary) is provided with the code for this chapter. You might need to re-add the 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.
ClassRecord
Figure 17.8 contains classRecordthat Figs. 17.9, 17.11 and 17.12 use for maintaining the information in each record that’s written to or read from a file. This class also belongs to theBankLibraryDLL, so it’s located in the same project as classBankUIForm.
74 values[ ( int ) TextBoxIndices.LAST ] = lastNameTextBox.Text;
75 values[ ( int ) TextBoxIndices.BALANCE ] = balanceTextBox.Text;
76
77 return values;
78 } // end method GetTextBoxValues 79 } // end class BankUIForm
80 } // end namespace BankLibrary
1 // Fig. 17.8: Record.cs
2 // Class that represents a data record.
3
4 namespace BankLibrary 5 {
6 public class Record
7 {
8 9 10 11 12 13 14 15
Fig. 17.8 | Record for sequential-access file-processing apps. (Part 1 of 2.) Fig. 17.7 | Base class for GUIs in our file-processing apps. (Part 3 of 3.)
// auto-implemented Account property public int Account { get; set; } // auto-implemented FirstName property public string FirstName { get; set; } // auto-implemented LastName property public string LastName { get; set; }
17.5 Creating a Sequential-Access Text File 685
Class Record contains auto-implemented properties for instance variables Account,
FirstName,LastNameandBalance(lines 9–18), which collectively represent all the infor- mation for a record. The parameterless constructor (lines 21–24) sets these members by calling the four-argument constructor with0for the account number,string.Emptyfor the first and last name and0.0Mfor the balance. The four-argument constructor (lines 27–
34) sets these members to the specified parameter values.
Using a Character Stream to Create an Output File
ClassCreateFileForm(Fig. 17.9) uses instances of classRecordto create asequential-access filethat might be used in an accounts-receivable system—i.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 app, the account number is used as therecord key—files are created and maintained in account- number order. This program assumes that the user enters records in account-number or- der. However, a comprehensive accounts-receivable system would provide asortingcapa- bility, so the user could enter the records in any order.
16 17 18 19
20 // parameterless constructor sets members to default values 21 public Record()
22 : this( 0, string.Empty, string.Empty, 0M )
23 {
24 } // end constructor 25
26 // overloaded constructor sets members to parameter values 27 public Record( int accountValue, string firstNameValue, 28 string lastNameValue, decimal balanceValue )
29 {
30 Account = accountValue;
31 FirstName = firstNameValue;
32 LastName = lastNameValue;
33 Balance = balanceValue;
34 } // end constructor 35 } // end class Record 36 } // end namespace BankLibrary
1 // Fig. 17.9: CreateFileForm.cs 2 // Creating a sequential-access file.
3 using System;
4 using System.Windows.Forms;
5 using System.IO;
Fig. 17.9 | Creating and writing to a sequential-access file. (Part 1 of 5.) Fig. 17.8 | Record for sequential-access file-processing apps. (Part 2 of 2.)
// auto-implemented Balance property public decimal Balance { get; set; }
6 using BankLibrary;
7
8 namespace CreateFile 9 {
10
11 {
12 13
14 // parameterless constructor 15 public CreateFileForm()
16 {
17 InitializeComponent();
18 } // end constructor 19
20 // event handler for Save Button
21 private void saveButton_Click( object sender, EventArgs e )
22 {
23 // create and show dialog box enabling user to save file 24 DialogResult result; // result of SaveFileDialog
25 string fileName; // name of file containing data 26
27 28 29 30 31 32 33
34 // ensure that user clicked "OK"
35 if ( result == DialogResult.OK )
36 {
37 // show error if user specified invalid file 38 if ( fileName == string.Empty )
39 MessageBox.Show( "Invalid File Name", "Error",
40 MessageBoxButtons.OK, MessageBoxIcon.Error );
41 else
42 {
43 // save file via FileStream if user specified valid file
44 try
45 {
46 // open file with write access
47 48 49
50 // sets file to where data is written 51
52
53 // disable Save button and enable Enter button
54 saveButton.Enabled = false;
55 enterButton.Enabled = true;
56 } // end try
57 // handle exception if there's a problem opening the file
Fig. 17.9 | Creating and writing to a sequential-access file. (Part 2 of 5.)
public partial class CreateFileForm : BankUIForm
private StreamWriter fileWriter; // writes data to text file
using ( SaveFileDialog fileChooser = new SaveFileDialog() ) {
fileChooser.CheckFileExists = false; // let user create file result = fileChooser.ShowDialog();
fileName = fileChooser.FileName; // name of file to save data } // end using
FileStream output = new FileStream( fileName, FileMode.OpenOrCreate, FileAccess.Write );
fileWriter = new StreamWriter( output );
17.5 Creating a Sequential-Access Text File 687
58 catch ( IOException )
59 {
60 // notify user if file does not exist
61 MessageBox.Show( "Error opening file", "Error",
62 MessageBoxButtons.OK, MessageBoxIcon.Error );
63 } // end catch
64 } // end else
65 } // end if
66 } // end method saveButton_Click 67
68 // handler for enterButton Click
69 private void enterButton_Click( object sender, EventArgs e )
70 {
71 // store TextBox values string array 72 string[] values = GetTextBoxValues();
73
74 // Record containing TextBox values to output 75 Record record = new Record();
76
77 // determine whether TextBox account field is empty
78 if ( values[ ( int ) TextBoxIndices.ACCOUNT ] != string.Empty )
79 {
80 // store TextBox values in Record and output it
81 try
82 {
83 // get account-number value from TextBox 84 int accountNumber = Int32.Parse(
85 values[ ( int ) TextBoxIndices.ACCOUNT ] );
86
87 // determine whether accountNumber is valid 88 if ( accountNumber > 0 )
89 {
90 // store TextBox fields in Record
91 record.Account = accountNumber;
92 record.FirstName = values[ ( int )
93 TextBoxIndices.FIRST ];
94 record.LastName = values[ ( int )
95 TextBoxIndices.LAST ];
96 record.Balance = Decimal.Parse(
97 values[ ( int ) TextBoxIndices.BALANCE ] );
98
99 // write Record to file, fields separated by commas 100
101 102
103 } // end if
104 else
105 {
106 // notify user if invalid account number
107 MessageBox.Show( "Invalid Account Number", "Error",
108 MessageBoxButtons.OK, MessageBoxIcon.Error );
109 } // end else
110 } // end try
Fig. 17.9 | Creating and writing to a sequential-access file. (Part 3 of 5.)
fileWriter.WriteLine(
record.Account + "," + record.FirstName + "," + record.LastName + "," + record.Balance );
111 // notify user if error occurs during the output operation 112 catch ( IOException )
113 {
114 MessageBox.Show( "Error Writing to File", "Error",
115 MessageBoxButtons.OK, MessageBoxIcon.Error );
116 } // end catch
117 // notify user if error occurs regarding parameter format 118 catch ( FormatException )
119 {
120 MessageBox.Show( "Invalid Format", "Error",
121 MessageBoxButtons.OK, MessageBoxIcon.Error );
122 } // end catch
123 } // end if
124
125 ClearTextBoxes(); // clear TextBox values 126 } // end method enterButton_Click
127
128 // handler for exitButton Click
129 private void exitButton_Click( object sender, EventArgs e )
130 {
131 // determine whether file exists 132 if ( fileWriter != null )
133 {
134 try
135 {
136 // close StreamWriter and underlying file 137
138 } // end try
139 // notify user of error closing file 140 catch ( IOException )
141 {
142 MessageBox.Show( "Cannot close file", "Error",
143 MessageBoxButtons.OK, MessageBoxIcon.Error );
144 } // end catch
145 } // end if
146
147 Application.Exit();
148 } // end method exitButton_Click 149 } // end class CreateFileForm 150 } // end namespace CreateFile
Fig. 17.9 | Creating and writing to a sequential-access file. (Part 4 of 5.)
fileWriter.Close();
a) BankUI graphical user interface with three additional controls
17.5 Creating a Sequential-Access Text File 689
Class CreateFileForm either creates or opens a file (depending on whether one exists), then allows the user to write records to it. Theusingdirective in line 6 enables us to use the classes of theBankLibrarynamespace; this namespace contains classBankUI-
Form, from which classCreateFileForminherits (line 10). ClassCreateFileForm’s GUI enhances that of classBankUIFormwith buttonsSave As,EnterandExit.
MethodsaveButton_Click
When the user clicks the Save As button, the program invokes the event handler
saveButton_Click(lines 21–66). Line 27 instantiates an object of classSaveFileDialog
(namespaceSystem.Windows.Forms). By placing this object in ausingstatement (lines 27–32), we can ensure that the dialog’sDisposemethod is called to release its resources as soon as the program has retrieved user input from it.SaveFileDialogobjects are used for selecting files (see the second screen in Fig. 17.9). Line 29 indicates that the dialog should not check if the file name specified by the user already exists (this is actually the default).
Fig. 17.9 | Creating and writing to a sequential-access file. (Part 5 of 5.)
Files and directories b) Save File dialog
c) Account 100,
"Nancy Brown", saved with a balance of -25.54
Line 30 callsSaveFileDialogmethodShowDialogto display the dialog. When displayed, aSaveFileDialogprevents the user from interacting with any other window in the pro- gram until the user closes theSaveFileDialogby clicking eitherSaveorCancel. Dialogs that behave in this manner are calledmodal dialogs. The user selects the appropriate drive, directory and file name, then clicksSave. MethodShowDialogreturns a DialogResult
specifying which button (SaveorCancel) the user clicked to close the dialog. This is as- signed toDialogResultvariableresult(line 30). Line 31 gets the file name from the di- alog. Line 35 tests whether the user clicked OK by comparing this value to
DialogResult.OK. If the values are equal, methodsaveButton_Clickcontinues.
You can open files to perform text manipulation by creating objects of classFileStream. In this example, we want the file to be opened foroutput, so lines 47–48 create aFileStream object. TheFileStreamconstructor that we use receives three arguments—astringcon- taining 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 constantFileMode.OpenOrCreate(line 48) indicates that theFileStreamobject should open the file if itexistsor create the file if itdoes not exist. Note that the contents of an existing file areoverwrittenby theStreamWriter. To preservethe original contents of a file, useFileMode.Append. There are otherFileModecon- stants describing how to open files; we introduce these constants as we use them in examples.
The constantFileAccess.Writeindicates that the program can perform onlywriteopera- tions with theFileStreamobject. There are two other constants for the third constructor parameter—FileAccess.Readfor read-only access and FileAccess.ReadWrite for both read and write access. Line 58 catches anIOExceptionif there’s a problem opening the file or creating theStreamWriter. If so, the program displays an error message (lines 61–62). If no exception occurs, the file is open for writing.
MethodenterButton_Click
After typing information into each TextBox, the user clicks Enter, which calls enter-
Button_Click(lines 69–126) to save the data from theTextBoxes into the user-specified file. If the user entered avalidaccount number (i.e., an integer greater than zero), lines 91–
97 store theTextBox values in an object of typeRecord(created at line 75). If the user enteredinvaliddata in one of theTextBoxes (such as nonnumeric characters in theBalance
field), the program throws aFormatException. Thecatchblock in lines 118–122 handles such exceptions by notifying the user (via aMessageBox) of the improper format.
If the user enteredvaliddata, lines 100–102 write the record to the file by invoking method WriteLine of the StreamWriter object that was created at line 51. Method
WriteLine writes a sequence of characters to a file. The StreamWriter object is con- structed with aFileStreamargument that specifies the file to which theStreamWriter
will output text. ClassStreamWriter(like most of the classes we discuss in this chapter) belongs to t he System.IOnamespace.
Good Programming Practice 17.1
When opening files, use theFileAccessenumerationto control user access to these files.
Common Programming Error 17.1
Failure to open a file before attempting to use it in a program is a logic error.