Home

Introduction to File Processing

 

Documents

 

Introduction

When creating an application, if it is a word processor, after opening the application, it may display an empty area in which the user can start typing. If the user is working on a spreadsheet, he or she may start typing numbers and performing calculations. In the same way, a user who is facing a graphics application would start drawing in it.

The object that a user would be using is called a document. After working on a document for a while, there are some other automatic ideas that come in the mind of a user. One would lead to saving the document. Another would consist of printing it. All these routine operations should be available to the user. This aspect of the application is taken care of by the person who creates the application. Not every application allows a user to save or to print its documents. If you want these operations to be possible, you must (explicitly) provide them.

Windows Common Dialog Boxes

To support the various operations of saving a document, opening an existing document, printing a document, setting up printing, etc, Microsoft Windows provides a series of standard dialog boxes that are available almost regardless of the programming environment you are using to develop your application. These common dialog boxes are stored in libraries (DLLs) that ship with the operating system but they may be provided in a raw format. For this reason, except if programming in Win32, the programming environment you use provides a customized and friendlier technique of adding these dialog boxes to your application. In the same way, the .NET Framework provides its own implementation of these ready-made dialog boxes in a manner that makes it easier to implement them.

To use a standard Windows dialog box, from the Toolbox, click the button that corresponds to the dialog box you want to add and click the form anywhere. The position of the control on the form has no importance because it is only a representative: it will not appear when the form is running. Once the desired dialog's icon is on the form, place a button on the form or create a menu item that will be used to call the dialog box.

Overview of File Processing and Definitions

 

Introduction

A piece of information used in an application is primarily represented as a group of bits. So far, if we requested information from the user, when the application exited, we lost all information that the user had entered. This is because such information was only temporarily stored in the random access memory (RAM). In some cases, you will want to "keep" information that the user has entered so you can make the information available the next time the user opens the application. In some other cases, whether you request information from the user or inherently provide it to the user, you may want different people working from different computers to use or share the same data. In these and other scenarios, you must store the information somewhere and retrieve it when necessary. This is the basis of file processing.

Files

A file is a series of bytes of data that are arranged in a particular manner to produce a usable document. For easy storage, location, and management, the bytes are stored on a medium such as a hard disc, a floppy disc, a compact disc, or any valid and supported type of storage. When these bytes belong to a single but common entity and hold values that are stored on a medium, the group is referred to as a file.

For greater management, files can be stored in a parent object called a directory or a folder. Since a file is a unit of storage and it stores information, it has a size, which is the number of bits it uses to store its values. To manage it, a file has a location also called a path that specifies where and/or how the file can be retrieved. Also, for better management, a file has attributes (characteristics) that indicate what can be done on the file or that provide specific information that the programmer or the operating system can use when dealing with the file.

Streams

File processing consists of creating, storing, and/or retrieving the contents of a file from a recognizable medium. For example, it is used to save word-processed files to a hard drive, to store a presentation on floppy disk, or to open a file from a CD-ROM. A stream is the technique or means of performing file processing. In order to manage files stored in a computer, each file must be able to provide basic pieces of information about itself. This basic information is specified when the file is created but can change during the lifetime of a file.

To create a file, a user must first decide where it would be located: this is a requirement. A file can be located on the root drive. Alternatively, a file can be positioned inside of an existing folder. Based on security settings, a user may not be able to create a file just anywhere in the (file system of the) computer. Once the user has decided where the file would reside, there are various means of creating files that the users are trained to use. When creating a file, the user must give it a name following the rules of the operating system combined with those of the file system. The most fundamental piece of information a file must have is a name.

Once the user has created a file, whether the file is empty or not, the operating system assigns basic pieces of information to it. Once a file is created, it can be opened, updated, modified, renamed, etc.

Streaming Prerequisites

 

Introduction

To support file processing, the .NET Framework provides the System.IO namespace that contains many different classes to handle almost any type of file operation you may need to perform. Therefore, to perform file processing, you can include the System.IO namespace in your project.

The parent class of file processing is Stream:

type Stream =  
    class 
        inherit MarshalByRefObject 
        interface IDisposable 
    end

With Stream, you can store data to a stream or you can retrieve data from a stream. Stream is an abstract class, which means that you cannot use it to declare a variable in your application. As an abstract class, Stream is used as the parent of the classes that actually implement the necessary operations. You will usually use a combination of classes to perform a typical operation. For example, some classes are used to create a stream object while some others are used to write data to the created stream.

The Name of a File

Before performing file processing, one of your early decisions will consist of specifying the type of operation you want the user to perform. For example, the user may want to create a brand new file, open an existing file, or perform a routine operation on a file. In all or most cases, whether you are creating a new file or manipulating an existing one, you must specify the name of the file. You can do this by declaring a string variable but, as we will learn later on, most classes used to create a stream can take a string that represents the file.

If you are creating a new file, there are certainly some rules you must observe. The name of a file follows the directives of the operating system. On MS DOS and Windows 3.X (that is, prior to Microsoft Windows 9X), the file had to use the 8.3 format. The actual name had to have a maximum of 8 characters with restrictions on the characters that could be used. The user also had to specify three characters after a period. The three characters, known as the file extension, were used by the operating system to classify the file. That was all necessary for those 8-bit and 16-bit operating systems. Various rules have changed. For example, the names of folders and files on Microsoft Windows >= 95 can have up to 255 characters. The extension of the file is mostly left to the judgment of the programmer but the files are still using extensions. Applications can also be configured to save different types of files; that is, files with different extensions.

Author Note At the time of this writing, the rules for file names for Microsoft Windows were on the MSDN web site at Windows Development\Windows Base Services\Files and I/O\SDK Documentation\Storage\Storage Overview\File Management\Creating, Deleting, and Maintaining Files\Naming a File (because it is a web site and not a book, its pages can change anytime).

Based on this, if you declare a string variable to hold the name of the file, you can simply initialize the variable with the necessary name and its extension. Here is an example:

open System
open System.Windows.Forms

let exercise : Form = new Form()

let strFilename = "Employees.spr"

[<STAThread>]
[<EntryPoint>]
let main argv = 
    Application.Run exercise
    0

The Path to a File

If you declare a string as above, the file will be created in the folder as the application. Otherwise, you can create your new file anywhere in the hard drive. To do that, you must provide a complete path where the file will reside. A path is a string that specifies the drive (such as A:, C:, or D:). The sections of a complete path string are separated by a backslash. For example, a path can the made of a folder followed by the name of the file. An example would be

C:\Palermo.tde 

A path can also consist of a drive followed by the name of the folder in which the file will be created. Here is an example:

C:\Program Files\Palermo.tde

A path can also indicate that the file will be created in a folder that itself is inside of another folder. In this case, remember that the names of folder must be separated by backslashes.

The backslash character is used to create or manage escape sequences and it can be included in a string value to make up an escape sequence. Because of this, every time you include a backslash in a string, the compiler thinks that you are trying to provide an escape sequence. In this case, if the combination of the backslash and the character that follows the backslash is not recognized as an escape sequence, you would get an error. To solve this problem, you have two alternatives. To indicate that the backslash must be considered as a character in its own right, you can double it. Here are examples:

let strFilename = "C:\\Documents and Settings\\Employees.spr"

Alternative, you can keep one backslash in each placeholder but precede the value of the string with the @ symbol. Here is an example:

let strFilename = @"C:\Documents and Settings\Employees.spr"

In the same way, you can declare a string variable to represent the name of an existing file that you plan to use in your program. You can also represent its path.

When providing a path to the file, if the drive you specify doesn't exist or cannot be read, the compiler would consider that the file doesn't exist. If you provide folders that don't exist in the drive, the compiler would consider that the file doesn't exist. This also means that the compiler will not create the folder(s) (the .NET Framework provides all means to create a folder but you must ask the compiler to create it; simply specifying a folder that doesn't exist will not automatically create it, even if you are creating a new file). Therefore, it is your responsibility to make sure that either the file or the path to the file is valid. As we will see in the next section, the compiler can check the existence of a file or path.

The .NET Framework Support for Files

 

Introduction

The primary support of a file as an object is provided by a .NET Framework class called File:

type File =  class end

This static class is equipped with various types of (static) methods to create, save, open, copy, move, delete, or check the existence of a file.

File Existence

One of the valuable operations that the File class can perform is to check the existence of the file you want to use. For example, if you are creating a new file, you may want to make sure it doesn't exist already because if you try to create a file that exists already, the compiler may first delete the old file before creating the new one. This could lead to unpredictable result, especially because such a file is not sent to the Recycle Bin. On the other hand, if you are trying to open a file, you should first make sure the file exists, otherwise the compiler will not be able to open a file it cannot find.

To check the existence of a file, the File class provides the Exists method. Its syntax is:

static member Exists : 
        path:string -> bool

If you provide only the name of the file, the compiler would check it in the folder of the application. If you provide the path to the file, the compiler would check its drive, its folder(s) and the file itself. In both cases, if the file exists, the method returns true. If the compiler cannot find the file, the method returns false. It's important to know that if you provided a complete path to the file, any slight mistake would produce a false result.

File Creation

Besides checking the existence of the file, the File class can be used to create a new file. To support this operation, the File class is equipped with the Create() method that is overloaded with two versions as follows:

static member Create : 
        path:string -> FileStream
static member Create : 
        path:string * 
        bufferSize:int -> FileStream

In both cases, the File.Create() method returns a Stream value, in this case a FileStream value. As the File.Create() method indicates, it takes the name or path of the file as argument. If you know or want to specify the size, in bytes, of the file, you can use the second version.

To provide the same operation of creating a file, you can use the Open() method of the File class. It is overloaded in three versions as follows:

static member Open : 
        path:string * 
        mode:FileMode -> FileStream
static member Open : 
        path:string * 
        mode:FileMode * 
        access:FileAccess -> FileStream
static member Open : 
        path:string * 
        mode:FileMode * 
        access:FileAccess * 
        share:FileShare -> FileStream

Access to a File

In order to perform an operation on a file, you must specify to the operating system how to proceed. One of the options you have is to indicate the type of access that will be granted on the file. This access is specified using the FileAccess enumeration. The members of the FileAccess enumeration are:

  • FileAccess.Write: New data can be written to the file
  • FileAccess.Read: Existing data can be read from the file
  • FileAccess.ReadWrite: Existing data can be read from the file and new data be written to the file

File Sharing

In standalone workstations, one person is usually able to access and open a file then perform the necessary operations on it. In networked computers, you may create a file that different people can access at the same time or you may make one file access another file to retrieve information. For example, suppose you create an application for a fast food restaurant that has two or more connected workstations and all workstations save their customers orders to a common file. In this case, you must make sure that any of the computers can access the file to save an order. An employee from one of these workstations must also be able to open the file to retrieve a customer order for any necessary reason. You can also create a situation where one file holds an inventory of the items of a store and another file holds the customers orders. Obviously one file would depend on another. Based on this, when an operation must be performed on a file, you may have to specify how a file can be shared. This is done through the FileShare enumeration.

The values of the FileShare enumeration are:

  • FileShare.Inheritable: Allows other file handles to inherit from this file
  • FileShare.None: The file cannot be shared
  • FileShare.Read: The file can be opened and read from
  • FileShare.Write: The file can be opened and written to
  • FileShare.ReadWrite: The file can be opened to write to it or read from it

The Mode of a File

Besides the access to the file, another option you will most likely specify to the operating system is referred to as the mode of a file. It is specified through the FileMode enumeration. The members of the FileMode enumeration are:

  • FileMode.Append: If the file already exists, the new data will be added to its end. If the file doesn't exist, it will be created and the new data will be added to it
  • FileMode.Create: If the file doesn't exist, it will be created. If the file already exists, it will be deleted and a new file with the same name will be created (in the same location)
  • FileMode.CreateNew: If the file doesn't exist, it will be created. If the new already exists, the compiler will throw an error
  • FileMode.Open: If the file exists, it will be opened. If the file doesn't exist, an error would be thrown
  • FileMode.OpenOrCreate: If the file already exists, it will be opened. If the file doesn't exist, it will be created
  • FileMode.Truncate: If the file already exists, its contents will be deleted completely but the file will be kept, allowing you to write new data to it. If the file doesn't exist, an error would be thrown
   
 

Fundamentals of File Streaming

 

Introduction

File streaming consists of performing one of the routine operations on a file, such as creating or opening it. This basic operation can be performed using a class called FileStream:

type FileStream =  
    class 
        inherit Stream 
    end

You can use a FileStream object to get a stream ready for processing. As one of the most complete classes of file processing of the .NET Framework, FileStream is equipped with all necessary properties and methods. To use it, you must first declare a variable of it. The class is equipped with nine constructors.

One of the constructors (the second) of the FileStream class has the following syntax:

new : 
        path:string * 
        mode:FileMode -> FileStream

This constructor takes as its first argument the name of the file or its path. The second argument specifies the type of operation to perform on the file. Here is an example:

open System
open System.IO
open System.Windows.Forms

let exercise : Form = new Form()

let strFilename = "C:\\Resources\\Employees.spr"

let fstEmployees : FileStream = new FileStream(strFilename, FileMode.Create)

[<STAThread>]
[<EntryPoint>]
let main argv = 
    Application.Run exercise
    0

Stream Writing

A streaming operation is typically used to create a stream. Once the stream is ready, you can write data to it. The writing operation is performed through various classes. One of these classes is BinaryWriter:

type BinaryWriter =  
    class 
        interface IDisposable 
    end

The BinaryWriter class can be used to write values of primitive data types (char, int, float, double, etc). To use a BinaryWriter value, you can first declare its variable. To do this, you would use one of the class' three constructors. The first constructor is the default, meaning it doesn't take an argument. The second constructor has the following signature:

new :
        output:Stream -> BinaryWriter

This constructor takes as argument a Stream value, which could be a FileStream variable. Here is an example:

open System
open System.IO
open System.Windows.Forms

let exercise : Form = new Form()

let strFilename = "C:\\Resources\\Employees.spr"

let fstEmployees : FileStream = new FileStream(strFilename, FileMode.Create)

let wrtEmployees : BinaryWriter = new BinaryWriter(fstEmployees)

[<STAThread>]
[<EntryPoint>]
let main argv = 
    Application.Run exercise
    0

Most classes that are used to add values to a stream are equipped with a method called Write. This is also the case for the BinaryWriter class. This method takes as argument the value that must be written to the stream. The method is overloaded so that there is a version for each primitive data type. Here are examples that add values (an integer, a string, and a floating-point value) to a newly created file:

let btnSaveClick e =
    let fstLoanProcessing : FileStream = new FileStream("Loan,lnp", FileMode.Create)
    let bwLoanProcessing : BinaryWriter = new BinaryWriter(fstLoanProcessing)

    bwLoanProcessing.Write(int txtAccountNumber.Text)
    bwLoanProcessing.Write(txtCustomerName.Text)
    bwLoanProcessing.Write(float txtLoanAmount.Text)
btnSave.Click.Add btnSaveClick

Stream Closing

When you use a stream, it requests resources from the operating system and uses them while the stream is available. When you are not using the stream anymore, you should free the resources and make them available again to the operating system so that other services can use them. This is done by closing the stream.

To close a stream, you can can call the Close() method of the class(es) you were using. Here are examples:

File Processing - Writing to a Stream

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic

// Form: Watts  Loan
let wattsALoan : Form = new Form(MaximizeBox = false, ClientSize = new System.Drawing.Size(265, 441),
                                 StartPosition = FormStartPosition.CenterScreen, Text = "Watts A Loan - Processing")

// Label: Customer Information
wattsALoan.Controls.Add(new Label(TabIndex = 0, ForeColor = Color.White, Location = new Point(23, 19),
                                  Text = "Customer Information", BackColor = Color.FromArgb(64, 64, 64),
                                  Size = new System.Drawing.Size(218, 20),
                                  Font = new Font("Bell MT", 12.00F, FontStyle.Bold, GraphicsUnit.Point, byte 0)))

// Account Number
wattsALoan.Controls.Add(new Label(AutoSize = true, Location = new Point(23, 54), TabIndex = 1, Text = "Account #:"))
        
let rndAccountNumber : Random = new Random()

let txtAccountNumber = new TextBox(Size = new System.Drawing.Size(128, 20),
                                   Location = new Point(113, 51), TabIndex = 2)
txtAccountNumber.Text <- sprintf "%i" (rndAccountNumber.Next(100001, 999999))
wattsALoan.Controls.Add txtAccountNumber

// First Name
wattsALoan.Controls.Add(new Label(AutoSize = true, Location = new Point(23, 80), TabIndex = 3, Text = "First Name:"))
let txtFirstName = new TextBox(Location = new Point(113, 77), Size = new System.Drawing.Size(128, 20), TabIndex = 4)
wattsALoan.Controls.Add txtFirstName

// Last  Name
wattsALoan.Controls.Add(new Label(AutoSize = true, Location = new Point(23, 106), TabIndex = 5, Text = "Last Name:"))
let txtLastName = new TextBox(Location = new Point(113, 103), Size = new System.Drawing.Size(128, 20), TabIndex = 6)
wattsALoan.Controls.Add txtLastName

// Loan Information
wattsALoan.Controls.Add(new Label(TabIndex = 7, ForeColor = Color.White, Text = "Loan Information",
                                  Location = new Point(23, 140), BackColor = Color.FromArgb(64, 64, 64),
                                  Size = new System.Drawing.Size(218, 20),
                                  Font = new Font("Bell MT", 12.00F, FontStyle.Bold, GraphicsUnit.Point, byte 0)))

// Label: Loan Amount
wattsALoan.Controls.Add(new Label(TabIndex = 8, AutoSize = true, Text = "Amount of Loan:", Location = new Point(22, 180)))
let txtLoanAmount = new TextBox(TabIndex = 9, Text = "0.00", Location = new Point(113, 177),
                                Size = new System.Drawing.Size(128, 20))
wattsALoan.Controls.Add txtLoanAmount

// Loan Type
wattsALoan.Controls.Add(new Label(TabIndex = 10, AutoSize = true, Text = "Type of Loan:", Location = new Point(22, 207)))
let cbxLoanTypes = new ComboBox(TabIndex = 11, Location = new Point(113, 204),
                                Size = new System.Drawing.Size(128, 21))
cbxLoanTypes.Items.AddRange [| "Car"; "Boat"; "Personal"; "Musical Instrument" |]
wattsALoan.Controls.Add cbxLoanTypes

// Down Payment
wattsALoan.Controls.Add(new Label(TabIndex = 12, AutoSize = true, Text = "Down Payment:",
                                  Location = new Point(22, 233)))
let txtDownPaymentPercent = new TextBox(Text = "0.00", TabIndex = 15,
                                        Location = new Point(113, 230), Size = new System.Drawing.Size(41, 20))
wattsALoan.Controls.Add txtDownPaymentPercent

// Down Payment Percent
wattsALoan.Controls.Add(new Label(Text = "%", TabIndex = 14, AutoSize = true,
                                  Location = new Point(158, 233)))
let txtDownPaymentAmount = new TextBox(TabIndex = 16, Text = "0.00",
                                       Location = new Point(179, 230), Size = new System.Drawing.Size(62, 20))
wattsALoan.Controls.Add txtDownPaymentAmount

let txtDownPaymentPercentLeave e =
    let loanAmount = ref 0.00
    let downPaymentRate = ref 0.00

    try
        loanAmount := float txtLoanAmount.Text
    with
    | :? FormatException as exc -> MessageBox.Show("The loan amount rate you provided is incorrect", "Watts A Loan",
                                                   MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore

    try
        downPaymentRate := float txtDownPaymentPercent.Text
    with
    | :? FormatException as exc -> MessageBox.Show("The down payment rate you provided is incorrect", "Watts A Loan",
                                                   MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore

    let downPaymentAmount = !loanAmount * !downPaymentRate / 100.00
    txtDownPaymentAmount.Text <- sprintf "%0.02f" downPaymentAmount
txtDownPaymentPercent.Leave.Add txtDownPaymentPercentLeave

// Interest Rate
wattsALoan.Controls.Add(new Label(AutoSize = true, Location = new Point(22, 259),
                                  TabIndex = 28, Text = "Interest Rate:"))
                                       
let txtInterestRate = new TextBox(TabIndex = 29, Text = "0.00",
                                  Location = new Point(113, 256), Size = new System.Drawing.Size(41, 20))
wattsALoan.Controls.Add txtInterestRate

// Label: Interest Rate Percent
wattsALoan.Controls.Add(new Label(TabIndex = 30, Text = "%",
                                  AutoSize = true, Location = new Point(158, 259)))
// Label: Periods
wattsALoan.Controls.Add(new Label(AutoSize = true, TabIndex = 31,
                                  Text = "Periods:", Location = new Point(22, 285)))
let txtPeriods = new TextBox(Text = "1", Location = new Point(113, 282),
                             Size = new System.Drawing.Size(41, 20), TabIndex = 32)
wattsALoan.Controls.Add txtPeriods
// Label: Years
wattsALoan.Controls.Add(new Label(Text = "Years", TabIndex = 33,
                                  AutoSize = true, Location = new Point(160, 285)))

// Button: Calculate
let btnCalculate = new Button(Text = "Calculate", Location = new Point(113, 308),
                              Size = new System.Drawing.Size(128, 32), TabIndex = 34)
wattsALoan.Controls.Add btnCalculate

// Interest Paid
wattsALoan.Controls.Add(new Label(AutoSize = true, TabIndex = 35,
                                  Location = new Point(22, 349), Text = "Interest Paid:"))
let txtInterestPaid = new TextBox(TabIndex = 36, Text = "0.00",
                                  Location = new Point(113, 346), Size = new System.Drawing.Size(128, 20))
wattsALoan.Controls.Add txtInterestPaid

// Future Value
wattsALoan.Controls.Add(new Label(TabIndex = 37, Text = "Future Value:",
                                  AutoSize = true, Location = new Point(22, 375)))
let txtFutureValue = new TextBox(TabIndex = 38, Text = "0.00",
                                 Location = new Point(113, 372), Size = new System.Drawing.Size(128, 20))
wattsALoan.Controls.Add txtFutureValue

let cbxLoanTypesSelectedIndexChanged(e) =
    let mutable downPayment = 0.00
    let loanAmount = float txtLoanAmount.Text
    let mutable downPaymentRate = 10.00
    let mutable downPaymentAmt  = 10.00

    match cbxLoanTypes.Text with
    | "Car" when loanAmount >= 25000.00 -> downPaymentRate <- 20.00
    | "Boat" when loanAmount >= 35000.00 -> downPaymentRate <- 40.00
    | "Personal" when loanAmount >= 3500.00 -> downPaymentRate <- 12.50
    | "Musical Instrument" when loanAmount >= 5000.00 -> downPaymentRate <- 15.00
    | _ -> txtDownPaymentPercent.Text <- "0.00"
           txtDownPaymentAmount.Text <- "0.00"
           
    downPaymentAmt <- loanAmount * downPaymentRate / 100.00

    let strDownPaymentRate = sprintf "%0.02f" downPaymentRate
    let strDownPaymentAmt  = sprintf "%0.02f" downPaymentAmt

    txtDownPaymentPercent.Text <- strDownPaymentRate
    txtDownPaymentAmount.Text  <- strDownPaymentAmt

    txtInterestPaid.Text <- "0.00"
    txtFutureValue.Text  <- "0.00"

cbxLoanTypes.SelectedIndexChanged.Add cbxLoanTypesSelectedIndexChanged
wattsALoan.Controls.Add cbxLoanTypes

let btnCalculateClick(e) =
    let loanAmount     = ref 0.00
    let downPaymentAmt = ref 0.00
    let interestRate   = ref 0.00
    let periods        = ref 0.00

    try
        loanAmount := float txtLoanAmount.Text
    with
    | :? FormatException as exc -> MessageBox.Show("The loan amount is not valid.", "Watts A Loan",
                                          MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore

    try
        downPaymentAmt := float txtDownPaymentAmount.Text
    with
    | :? FormatException as exc -> MessageBox.Show("There is a problem with the down payment.", "Watts A Loan",
                                          MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore

    try
        interestRate := (float txtInterestRate.Text) / 100.00
    with
    | :? FormatException as exc -> MessageBox.Show("There is something wrong with the interest rate you provided.", "Watts A Loan",
                                          MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore

    try
        periods := float txtPeriods.Text
    with
    | :? FormatException as exc -> MessageBox.Show("The number of periods is wrong.", "Watts A Loan",
                                          MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore

    let principal = !loanAmount - !downPaymentAmt
    let interestAmount = principal * !periods * !interestRate

    let strInterestAmount = sprintf "%0.02f" interestAmount

    txtInterestPaid.Text  <- strInterestAmount
    txtFutureValue.Text <- sprintf "%0.02f" (principal + interestAmount)

btnCalculate.Click.Add btnCalculateClick
wattsALoan.Controls.Add btnCalculate

// Button: Save
let btnSave = new Button(Text = "Save...", Location = new Point(85, 406),
                         TabIndex = 48, Size = new System.Drawing.Size(75, 23))
wattsALoan.Controls.Add btnSave

// Dialog Box: Save Loan
let dlgSaveLoan = new SaveFileDialog()
dlgSaveLoan.Filter <- "Loan Processing Files (*.lpf)|*.lpf|All Files|"

let btnSaveClick e =
    dlgSaveLoan.FileName <- txtAccountNumber.Text
    
    if dlgSaveLoan.ShowDialog() = DialogResult.OK then
        let fstLoanProcessing : FileStream = new FileStream(dlgSaveLoan.FileName, FileMode.Create)
        let bwLoanProcessing : BinaryWriter = new BinaryWriter(fstLoanProcessing)

        bwLoanProcessing.Write(int txtAccountNumber.Text)
        bwLoanProcessing.Write(txtFirstName.Text)
        bwLoanProcessing.Write(txtLastName.Text)
        bwLoanProcessing.Write(float txtLoanAmount.Text)
        bwLoanProcessing.Write(cbxLoanTypes.Text)
        bwLoanProcessing.Write(float txtDownPaymentPercent.Text)
        bwLoanProcessing.Write(float txtDownPaymentAmount.Text)
        bwLoanProcessing.Write(float txtInterestRate.Text)
        bwLoanProcessing.Write(float txtPeriods.Text)
        bwLoanProcessing.Write(float txtInterestPaid.Text)
        bwLoanProcessing.Write(float txtFutureValue.Text)
        
        bwLoanProcessing.Close()
        fstLoanProcessing.Close()

        let rndAccountNumber : Random = new Random()
        
        txtAccountNumber.Text <- rndAccountNumber.Next(100001, 999999).ToString()
        txtFirstName.Text <- ""
        txtLastName.Text <- ""
        txtLoanAmount.Text <- "0.00"
        cbxLoanTypes.Text <- "Personal"
        txtDownPaymentPercent.Text <- "0.00"
        txtDownPaymentAmount.Text <- "0.00"
        txtInterestRate.Text <- "0.00"
        txtPeriods.Text <- "0"
btnSave.Click.Add btnSaveClick

// Button: Close
let btnClose = new Button(TabIndex = 39, Text = "Close",
                          Location = new Point(169, 406), Size = new System.Drawing.Size(70, 23))
let btnCloseClick e = wattsALoan.Close()
btnClose.Click.Add btnCloseClick
wattsALoan.Controls.Add btnClose

[<EntryPoint>]
[<STAThread>]
let main argv =
    Application.Run wattsALoan
    0

File Processing - Writing to a Stream

Stream Reading

As opposed to writing to a stream, you may want to read existing data from it. Before doing this, you can first specify your intent to the streaming class using the FileMode enumeration. This can be done using the FileStream class as follows:

let strFilename = "Employees.spr"
let fstPersons : FileStream = new FileStream(Filename, FileMode.Open)

Once the stream is ready, you can get prepared to read data from it. To support this, you can use the BinaryReader class:

type BinaryReader =  
    class 
        interface IDisposable 
    end

This class provides three constructors. One of the constructors (the first) has the following signature:

new : 
        input:Stream -> BinaryReader

This constructor takes as argument a Stream value, which could be a FileStream object. After declaring a FileStream variable using this constructor, you can read data from it. To support this, the class provides an appropriate method for each primitive data type. As done for the binary writer, after using the binary reader, free the resources it was using by closing its stream. Here is an example:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic

// Form: Watts  Loan
let wattsALoan : Form = new Form(MaximizeBox = false, ClientSize = new System.Drawing.Size(265, 398),
                                 StartPosition = FormStartPosition.CenterScreen, Text = "Watts A Loan - Processing")

// Label: Customer Information
wattsALoan.Controls.Add(new Label(TabIndex = 0, ForeColor = Color.White, Location = new Point(23, 19),
                                  Text = "Customer Information", BackColor = Color.FromArgb(64, 64, 64),
                                  Size = new System.Drawing.Size(218, 20),
                                  Font = new Font("Bell MT", 12.00F, FontStyle.Bold, GraphicsUnit.Point, byte 0)))

// Account Number
wattsALoan.Controls.Add(new Label(AutoSize = true, Location = new Point(23, 54), TabIndex = 1, Text = "Account #:"))

let txtAccountNumber = new TextBox(Size = new System.Drawing.Size(128, 20),
                                   Location = new Point(113, 51), TabIndex = 2)
wattsALoan.Controls.Add txtAccountNumber

// First Name
wattsALoan.Controls.Add(new Label(AutoSize = true, Location = new Point(23, 80), TabIndex = 3, Text = "First Name:"))
let txtFirstName = new TextBox(Location = new Point(113, 77), Size = new System.Drawing.Size(128, 20), TabIndex = 4)
wattsALoan.Controls.Add txtFirstName

// Last  Name
wattsALoan.Controls.Add(new Label(AutoSize = true, Location = new Point(23, 106), TabIndex = 5, Text = "Last Name:"))
let txtLastName = new TextBox(Location = new Point(113, 103), Size = new System.Drawing.Size(128, 20), TabIndex = 6)
wattsALoan.Controls.Add txtLastName

// Loan Information
wattsALoan.Controls.Add(new Label(TabIndex = 7, ForeColor = Color.White, Text = "Loan Information",
                                  Location = new Point(23, 140), BackColor = Color.FromArgb(64, 64, 64),
                                  Size = new System.Drawing.Size(218, 20),
                                  Font = new Font("Bell MT", 12.00F, FontStyle.Bold, GraphicsUnit.Point, byte 0)))

// Label: Loan Amount
wattsALoan.Controls.Add(new Label(TabIndex = 8, AutoSize = true, Text = "Amount of Loan:", Location = new Point(22, 180)))
let txtLoanAmount = new TextBox(TabIndex = 9, Text = "0.00", Location = new Point(113, 177),
                                Size = new System.Drawing.Size(128, 20))
wattsALoan.Controls.Add txtLoanAmount

// Loan Type
wattsALoan.Controls.Add(new Label(TabIndex = 10, AutoSize = true, Text = "Type of Loan:", Location = new Point(22, 207)))
let cbxLoanTypes = new ComboBox(TabIndex = 11, Location = new Point(113, 204),
                                Size = new System.Drawing.Size(128, 21))
cbxLoanTypes.Items.AddRange [| "Car"; "Boat"; "Personal"; "Musical Instrument" |]
wattsALoan.Controls.Add cbxLoanTypes

// Down Payment
wattsALoan.Controls.Add(new Label(TabIndex = 12, AutoSize = true, Text = "Down Payment:",
                                  Location = new Point(22, 233)))
let txtDownPaymentPercent = new TextBox(Text = "0.00", TabIndex = 15,
                                        Location = new Point(113, 230), Size = new System.Drawing.Size(41, 20))
wattsALoan.Controls.Add txtDownPaymentPercent

// Down Payment Percent
wattsALoan.Controls.Add(new Label(Text = "%", TabIndex = 14, AutoSize = true,
                                  Location = new Point(158, 233)))
let txtDownPaymentAmount = new TextBox(TabIndex = 16, Text = "0.00",
                                       Location = new Point(179, 230), Size = new System.Drawing.Size(62, 20))
wattsALoan.Controls.Add txtDownPaymentAmount

// Interest Rate
wattsALoan.Controls.Add(new Label(AutoSize = true, Location = new Point(22, 259),
                                  TabIndex = 28, Text = "Interest Rate:"))
                                       
let txtInterestRate = new TextBox(TabIndex = 29, Text = "0.00",
                                  Location = new Point(113, 256), Size = new System.Drawing.Size(41, 20))
wattsALoan.Controls.Add txtInterestRate

// Label: Interest Rate Percent
wattsALoan.Controls.Add(new Label(TabIndex = 30, Text = "%",
                                  AutoSize = true, Location = new Point(158, 259)))
// Label: Periods
wattsALoan.Controls.Add(new Label(AutoSize = true, TabIndex = 31,
                                  Text = "Periods:", Location = new Point(22, 285)))
let txtPeriods = new TextBox(Text = "1", Location = new Point(113, 282),
                             Size = new System.Drawing.Size(41, 20), TabIndex = 32)
wattsALoan.Controls.Add txtPeriods
// Label: Years
wattsALoan.Controls.Add(new Label(Text = "Years", TabIndex = 33,
                                  AutoSize = true, Location = new Point(160, 285)))

// Interest Paid
wattsALoan.Controls.Add(new Label(AutoSize = true, TabIndex = 35,
                                  Location = new Point(22, 306), Text = "Interest Paid:"))
let txtInterestPaid = new TextBox(TabIndex = 36, Text = "0.00",
                                  Location = new Point(113, 303), Size = new System.Drawing.Size(128, 20))
wattsALoan.Controls.Add txtInterestPaid

// Future Value
wattsALoan.Controls.Add(new Label(TabIndex = 37, Text = "Future Value:",
                                  AutoSize = true, Location = new Point(22, 332)))
let txtFutureValue = new TextBox(TabIndex = 38, Text = "0.00",
                                 Location = new Point(113, 329), Size = new System.Drawing.Size(128, 20))
wattsALoan.Controls.Add txtFutureValue

// Button: Open
let btnOpen = new Button(Text = "Open...", Location = new Point(85, 364),
                         TabIndex = 48, Size = new System.Drawing.Size(75, 23))
wattsALoan.Controls.Add btnOpen

// Dialog Box: Open Loan
let dlgOpenLoan = new OpenFileDialog()
dlgOpenLoan.Filter <- "Loan Processing Files (*.lpf)|*.lpf|All Files|"

let btnOpenClick e =    
    if dlgOpenLoan.ShowDialog() = DialogResult.OK then
        let fstLoanProcessing : FileStream = new FileStream(dlgOpenLoan.FileName, FileMode.Open)
        let brLoanProcessing : BinaryReader = new BinaryReader(fstLoanProcessing)

        txtAccountNumber.Text <- sprintf "%i" (brLoanProcessing.ReadInt32())
        txtFirstName.Text <- brLoanProcessing.ReadString()
        txtLastName.Text <- brLoanProcessing.ReadString()
        txtLoanAmount.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
        cbxLoanTypes.Text <- brLoanProcessing.ReadString()
        txtDownPaymentPercent.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
        txtDownPaymentAmount.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
        txtInterestRate.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
        txtPeriods.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
        txtInterestPaid.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
        txtFutureValue.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
        
        brLoanProcessing.Close()
        fstLoanProcessing.Close()
btnOpen.Click.Add btnOpenClick

// Button: Close
let btnClose = new Button(TabIndex = 39, Text = "Close",
                          Location = new Point(166, 364), Size = new System.Drawing.Size(75, 23))
let btnCloseClick e = wattsALoan.Close()
btnClose.Click.Add btnCloseClick
wattsALoan.Controls.Add btnClose

[<EntryPoint>]
[<STAThread>]
let main argv =
    Application.Run wattsALoan
    0

Exception Handling in File Processing

 

Finally

In previous sections, to handle exceptions, we were using the try .. with routine. We also know that, when you create a stream, the operating system must allocate resources and dedicate them to the file processing operations. Additional resources may be provided for the object that is in charge of writing to, or reading from, the stream. We also saw that, when the streaming was over, we should free the resources and give them back to the operating system. To do this, we called the Close() method of the variable that was using resources.

More than any other assignment, file processing is in prime need of exception handling. During file processing, there are many things that can go wrong. For this reason, the creation and/or management of streams should be performed in a try block to get ready to handle exceptions that would occur. Besides actually handling exceptions, the F# language provides a special keyword used free resources. This keyword is finally.

The finally keyword is used to create a section of an exception. Like with, a finally block cannot exist by itself. It can be created following a try section. The formula used would be:

try

finally

Based on this, the finally section has a body of its own. Because the finally clause always gets executed, you can include any type of code in it but it is usually appropriate to free the resources that were allocated previously. In the same way, you can use a finally section to free resources used when reading from a stream. Here is an example for a binary writer:

let btnSaveClick e =
    dlgSaveLoan.FileName <- txtAccountNumber.Text
    
    if dlgSaveLoan.ShowDialog() = DialogResult.OK then
        let fstLoanProcessing : FileStream = new FileStream(dlgSaveLoan.FileName, FileMode.Create)
        let bwLoanProcessing : BinaryWriter = new BinaryWriter(fstLoanProcessing)

        try
            bwLoanProcessing.Write(int txtAccountNumber.Text)
            bwLoanProcessing.Write(txtFirstName.Text)
            bwLoanProcessing.Write(txtLastName.Text)
            bwLoanProcessing.Write(float txtLoanAmount.Text)
            bwLoanProcessing.Write(cbxLoanTypes.Text)
            bwLoanProcessing.Write(float txtDownPaymentPercent.Text)
            bwLoanProcessing.Write(float txtDownPaymentAmount.Text)
            bwLoanProcessing.Write(float txtInterestRate.Text)
            bwLoanProcessing.Write(float txtPeriods.Text)
            bwLoanProcessing.Write(float txtInterestPaid.Text)
            bwLoanProcessing.Write(float txtFutureValue.Text)
        finally
            bwLoanProcessing.Close()
            fstLoanProcessing.Close()

Here is an example for a binary reader:

let btnOpenClick e =    
    if dlgOpenLoan.ShowDialog() = DialogResult.OK then
        let fstLoanProcessing : FileStream = new FileStream(dlgOpenLoan.FileName, FileMode.Open)
        let brLoanProcessing : BinaryReader = new BinaryReader(fstLoanProcessing)

        try
            txtAccountNumber.Text <- sprintf "%i" (brLoanProcessing.ReadInt32())
            txtFirstName.Text <- brLoanProcessing.ReadString()
            txtLastName.Text <- brLoanProcessing.ReadString()
            txtLoanAmount.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
            cbxLoanTypes.Text <- brLoanProcessing.ReadString()
            txtDownPaymentPercent.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
            txtDownPaymentAmount.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
            txtInterestRate.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
            txtPeriods.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
            txtInterestPaid.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
            txtFutureValue.Text <- sprintf "%0.02f" (brLoanProcessing.ReadDouble())
        finally
            brLoanProcessing.Close()
            fstLoanProcessing.Close()

Of course, since the whole block of code starts with a try section, it is used for exception handling. This means that you can add the necessary and appropriate with section(s) (but you don't have to).

.NET Framework Exception Handling for File Processing

In the previous lesson as our introduction to file processing, we behaved as if everything was alright. Unfortunately, file processing can be very strict in its assignments. Based on this, the .NET Framework provides various Exception-oriented classes to deal with almost any type of exception you can think of.

One of the most important aspects of file processing is the name of the file that will be dealt with. In some cases you can provide this name to the application or document. In some other cases, you would let the user specify the name of the path. Regardless of how the name of the file would be provided to the operating system, when this name is acted upon, the compiler is asked to work on the file. If the file doesn't exist, the operation cannot be carried. Furthermore, the compiler would throw an error. There are many other exceptions that can be thrown as a result of something going bad during file processing:

  • FileNotFoundException: This exception is thrown when a file has not been found
  • IOException: As mentioned already, during file processing, anything could go wrong. If you don't know what caused an error, you can throw the IOException exception.
   
 

Previous Copyright © 2015 FunctionX Next