17.3 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 apps. The next few examples 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 app. These programs have similar user interfaces, so we created reusable class BankUIForm (Fig. 17.2) to encapsulate the common GUI (see the screen capture in Fig. 17.2). Class BankUIForm (part of the BankLibrary project with this chapter’s examples) contains four Labels and four TextBoxes. Methods ClearTextBoxes (lines 22–30), SetTextBoxValues (lines 33–51) and GetTextBoxValues (lines 54–59) clear, set the text in and get the text from the TextBoxes, respectively. Using visual inheritance—presented in Section 15.13— you can extend this class to create the GUIs for several examples in this chapter. Recall that to reuse class BankUIForm, you must compile the GUI into a class library, then add a reference to the new class library’s DLL in each project that will reuse it (see Section 15.13).

Fig. 17.2 A reusable Windows Form for the examples in this chapter apps.

Alternate View

 1    // Fig. 17.2: 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 { get; set; } = 4; // number of TextBoxes
11
12          // enumeration constants specify TextBox indices
13          public enum TextBoxIndices {Account, First, Last, Balance}
14         
15          // parameterless constructor
16          public BankUIForm()
17          {
18             InitializeComponent();
19          }
20         
21          // clear all TextBoxes
22          public void ClearTextBoxes()
23          {
24             // iterate through every Control on form
25             foreach (Control guiControl in Controls)
26             {
27                // if Control is TextBox, clear it
28                (guiControl as TextBox)?.Clear();
29             }
30          }
31         
32          // set text box values to string-array values
33          public void SetTextBoxValues(string[] values)
34          {
35             // determine whether string array has correct length
36             if (values.Length != TextBoxCount)
37             {
38                // throw exception if not correct length
39                throw (new ArgumentException(
40                   $"There must be {TextBoxCount} strings in the array",
41                   nameof(values)));
42             }
43              else // set array values if array has correct length
44              {
45                 // set array values to TextBox values
46                 accountTextBox.Text = values[(int) TextBoxIndices.Account];
47                 firstNameTextBox.Text = values[(int) TextBoxIndices.First];
48                 lastNameTextBox.Text = values[(int) TextBoxIndices.Last];
49                 balanceTextBox.Text = values[(int) TextBoxIndices.Balance];
50              }
51          }
52         
53          // return TextBox values as string array
54          public string[] GetTextBoxValues()
55          {
56             return new string[] {
57                accountTextBox.Text, firstNameTextBox.Text,
58                lastNameTextBox.Text, balanceTextBox.Text};
59          }
60       }
61    }

Class Record

Figure 17.3 contains class Record that Figs. 17.4, 17.6 and 17.7 use for maintaining the information in each record that’s written to or read from a file. This class also belongs to the BankLibrary, so it’s located in the same project as class BankUIForm.

Fig. 17.3 Class that represents a data record.

Alternate View

 1    // Fig. 17.3: Record.cs
 2    // Class that represents a data record.
 3    namespace BankLibrary
 4    {
 5       public class Record
 6       {
 7          public int Account { get; set; }
 8          public string FirstName { get; set; }
 9          public string LastName { get; set; }
10          public decimal Balance { get; set; }
11
12          // parameterless constructor sets members to default values
13          public Record() : this(0, string.Empty, string.Empty, 0M) { }
14         
15          // overloaded constructor sets members to parameter values
16          public Record(int account, string firstName,
17             string lastName, decimal balance)
18          {
19             Account = account;
20             FirstName = firstName;
21             LastName = lastName;
22             Balance = balance;
23          }
24       }
25    }

Class Record contains auto-implemented properties Account, FirstName, LastName and Balance (lines 7–10), which collectively represent all the information for a record. The parameterless constructor (line 13) sets these members by calling the four-argument constructor with 0 for the account number, string.Empty for the first and last name and 0.0M for the balance. The four-argument constructor (lines 16–23) sets these members to the specified parameter values.

Using a Character Stream to Create an Output File

Class CreateFileForm (Fig. 17.4) uses instances of class Record to create a sequential-access file that 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 the record key—files 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. When you create a Windows Forms Application project for this app, be sure to add a reference to BankLibrary.dll and to change the base class from Form to BankUIForm. See Section 15.13 for information on adding a reference to a class library.

Fig. 17.4 Creating a sequential-access file.

Alternate View

  1    // Fig. 17.4: 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    namespace CreateFile
  9    {
 10       public partial class CreateFileForm : BankUIForm
 11       {
 12          private StreamWriter fileWriter; // writes data to text file
 13
 14          // parameterless constructor
 15          public CreateFileForm()
 16          {
 17             InitializeComponent();
 18          }
 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              using (var fileChooser = new SaveFileDialog())                   
 28              {                                                                
 29                 fileChooser.CheckFileExists = false; // let user create file  
 30                 result = fileChooser.ShowDialog();                            
 31                 fileName = fileChooser.FileName; // name of file to save data 
 32              }                                                                
 33         
 34              // ensure that user clicked "OK"
 35              if (result == DialogResult.OK)
 36              {
 37                 // show error if user specified invalid file
 38                 if (string.IsNullOrEmpty(fileName))
 39                 {
 40                    MessageBox.Show("Invalid File Name", "Error",
 41                       MessageBoxButtons.OK, MessageBoxIcon.Error);
 42                 }
 43                 else
 44                 {
 45                    // save file via FileStream
 46                    try
 47                    {
 48                       // open file with write access
 49                       var output = new FileStream(fileName,
 50                          FileMode.OpenOrCreate, FileAccess.Write);
 51         
 52                       // sets file to where data is written
 53                       fileWriter = new StreamWriter(output);
 54                      
 55                       // disable Save button and enable Enter button
 56                       saveButton.Enabled = false;
 57                       enterButton.Enabled = true;
 58                     }
 59                     catch (IOException)
 60                     {
 61                        // notify user if file does not exist
 62                        MessageBox.Show("Error opening file", "Error",
 63                           MessageBoxButtons.OK, MessageBoxIcon.Error);
 64                     }
 65                 }
 66             }
 67         }
 68         
 69         // handler for enterButton Click
 70         private void enterButton_Click(object sender, EventArgs e)
 71         {
 72            // store TextBox values string array
 73            string[] values = GetTextBoxValues();
 74        
 75             // determine whether TextBox account field is empty
 76             if (!string.IsNullOrEmpty(values[(int) TextBoxIndices.Account]))
 77             {
 78                // store TextBox values in Record and output it
 79                try
 80                {
 81                   // get account-number value from TextBox
 82                   int accountNumber =
 83                      int.Parse(values[(int) TextBoxIndices.Account]);
 84        
 85                    // determine whether accountNumber is valid
 86                    if (accountNumber > 0)
 87                    {
 88                       // Record containing TextBox values to output
 89                       var record = new Record(accountNumber,
 90                          values[(int) TextBoxIndices.First],
 91                          values[(int) TextBoxIndices.Last],
 92                          decimal.Parse(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                    }
 99                    else
100                    {
101                       // notify user if invalid account number
102                       MessageBox.Show("Invalid Account Number", "Error",
103                          MessageBoxButtons.OK, MessageBoxIcon.Error);
104                     }
105                 }
106                 catch (IOException)
107                 {
108                    MessageBox.Show("Error Writing to File", "Error",
109                       MessageBoxButtons.OK, MessageBoxIcon.Error);
110                 }
111                 catch (FormatException)
112                 {
113                     MessageBox.Show("Invalid Format", "Error",
114                        MessageBoxButtons.OK, MessageBoxIcon.Error);
115                 }
116             }
117        
118             ClearTextBoxes(); // clear TextBox values
119          }
120        
121          // handler for exitButton Click
122          private void exitButton_Click(object sender, EventArgs e)
123          {
124             try
125             {
126                fileWriter?.Close(); // close StreamWriter and underlying file
127             }
128             catch (IOException)
129             {
130                MessageBox.Show("Cannot close file", "Error",
131                   MessageBoxButtons.OK, MessageBoxIcon.Error);
132             }
133        
134             Application.Exit();
135          }
136      }
137   }

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

Method saveButton_Click

When the user clicks the Save As button, the program invokes the event handler saveButton_Click (lines 21–67). Line 27 instantiates an object of class SaveFileDialog (namespace System.Windows.Forms). By placing this object in a using statement (lines 27–32), we ensure that the dialog’s Dispose method is called to release its resources as soon as the program has retrieved the user’s input. SaveFileDialog objects are used for selecting files (see the second screen in Fig. 17.4). Line 29 indicates that the dialog should not check if the filename specified by the user already exists (this is actually the default). Line 30 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 filename, 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 30). Line 31 gets the filename from the dialog. Line 35 tests whether the user clicked OK by comparing this value to DialogResult.OK. If the values are equal, method saveButton_Click continues.

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 49–50 create a FileStream object. The FileStream constructor that we use receives three arguments—a 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 read-write permissions. The constant FileMode.OpenOrCreate (line 50) indicates that the FileStream object should open the file if it exists or create the file if it does not exist.

Note that the contents of an existing file are overwritten by the FileStream. To preserve the original contents of a file, use FileMode.Append. 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 parameter—FileAccess.Read for read-only access and FileAccess.Read-Write for both read and write access.

Line 59 catches an IOException if there’s a problem opening the file or creating the StreamWriter. If so, the program displays an error message (lines 62–63). If no exception occurs, the file is open for writing.

Common Programming Error 17.1

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

Method enterButton_Click

After typing information into each TextBox, the user clicks Enter, which calls enter-Button_Click (lines 70–119) 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 89– 92 create a Record containing the TextBox values. If the user entered invalid data in one of the TextBoxes (such as nonnumeric characters in the Balance field), the program throws a FormatException. The catch block in lines 111–115 handles such exceptions by notifying the user (via a MessageBox) of the improper format.1

If the user entered valid data, lines 95–97 write the record to the file by invoking method WriteLine of the StreamWriter object that was created at line 53. 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 (like most of the classes we discuss in this chapter) belongs to the System.IO namespace. Finally, the TextBoxes are cleared so the user can begin typing the next record’s data.

Method exitButton_Click

When the user clicks Exit, exitButton_Click (lines 122–135) executes. Line 126 closes the StreamWriter (if it is not null), which automatically closes the underlying FileStream. Then, line 134 terminates the program. Note that method Close is called in a try block. Method Close throws an IOException if the file or stream cannot be closed properly. In this case, it’s important to notify the user that the information in the file or stream might be corrupted.

Performance Tip 17.1

Releasing resources explicitly when they’re no longer needed makes them immediately available for reuse by other programs, thus improving resource utilization.

Sample Data

To test the program, we entered information for the accounts shown in Fig. 17.5. The program does not depict how the data records are stored 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 it in any text editor to see its contents.

Fig. 17.5 Sample data for the program of Fig. 17.4.

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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset