Home

Introduction to .NET Built-In Collection Classes

 

Enumerating a Collection

 

Introduction

Many applications use lists of values. To create these values, you can use a collection class. One of the most regular ways to access the members of a collection consists of visiting each and taking the appropriate action. The mechanism of visiting each member of a list is also referred to as enumerating.

To support the ability to visit each member of a collection, the .NET Framework provides two interfaces named IEnumerator and IEnumerable. The IEnumerator interface starts as follows:

type IEnumerator =
    interface
    end

The IEnumerable interface starts as follows:

type IEnumerable =
    interface
    end

Introduction to the IEnumerator Interface

To support enumerating on generic types, the .NET Framework provides a generic interface named IEnumerator. The generic IEnumerator<> interface provides the means of identifying the class that holds a list of the items that will be enumerated. The generic IEnumerator<> interface inherits from the non-generic IEnumerator and the non-generic IDisposable interfaces:

type IEnumerator<'T> =  
    interface 
        interface IDisposable 
        interface IEnumerator 
    end

To use the functionality of the IEnumerator interface, you must create a class that implements it. The role of the enumerator is to act on a collection. For this reason, the class should be prepared to receive an external collection. This can be done by passing it to a constructor of the enumerating class. The internal collection would be used in the enumerating class. The external collection would be the source of the values of the list that would be enumerated.

The Current Item of an Enumeration

When creating a list, you should have a type of tag, as a field, that allows you to monitor the item that is being currently accessed or used in the list. The IEnumerator interface provides a property that is used to identify the current member of the list. This property is called Current. Because the current item is meant to be viewed only, the Current property is a read-only member.

Besides its own version, the IEnumerator<> interface inherits another version of the Current property from its parent. The Current property of the IEnumerator interface is of type Object.

Resetting the Tag of the Current Item

Although you should be able to identify the current item at any time, when the application starts, before the collection can be enumerated, the tag that is used to monitor the current item should be set to a value before the beginning of the count.

While the collection is being used, at one moment you may want to reset the tag of the current item to its original position. To support this operation, the IEnumerator interface is equipped with a method named Reset that the IEnumerator<> interface inherits. Its signature is:

abstract Reset : unit -> unit

When using the implementer of the IEnumerator<> interface, if you try accessing an item beyond the maximum number of items, the compiler would throw an IndexOutOfRangeException exception.

Moving to the Next Item in the Enumerator

We know that, when using the items of a collection, one way you could locate one item from another is to be able to jump from one item to the next. This operation is also very important when enumerating a collection. To support this operation, the IEnumerator<> interface inherits the MoveNext() method from its parent. The signature of the generic IEnumerator<> interface is:

abstract MoveNext : unit -> bool

Disposing of the Enumerator

In some cases, the object of a generic class consumes resources. These resources should be freed when the object is not used anymore. To assist you with cleaning memory, the IEnumerator<> interface implements the IDisposable interface. This interface has only one method, named Dispose:

abstract Dispose : unit -> unit

If you are creating a generic class that is not concerned with resources and would let the compiler take care of that, you can simply provide a body for this method. On the other hand, if you anticipate that the resources used by your class will need to be deallocated, make sure you implement this method properly.

An Enumerable Collection

 

Introduction

The IEnumerator<> interface is used to set up a collection for enumeration. The IEnumerator<> class doesn't provide the functionality necessary to use foreach. The next step is to implement another interface called IEnumerable.

While the IEnumerator<> (generic) interface is used to identify the class that holds each value that will be visited, the IEnumerable<> (generic) interface inherits from the IEnumerator (non-generic) interface that is used to communicate with the collection whose items will be enumerated. For this reason, when implementing the IEnumerable<> interface, you should provide the means of accessing the external collection. This can be done by passing a collection of the class that holds the values, to a constructor of the IEnumerable<> implementer.

Getting the Enumerator

While the class that implements the IEnumerator interface represents an object, the class that implements the interface is a collection. The new class does not know what collection it will be asked to enumerate. For this reason, the new class should have a member variable of the class that holds the values that will be enumerated.

For Each Member of an Enumerable

 

Introduction

To support the use of the for object in loop, the IEnumerable<> interface is equipped with a method named GetEnumerator. The IEnumerable<>.GetEnumerator() method returns an IEnumerator<> object. Its signature is:

abstract GetEnumerator : unit -> IEnumerator<'T>

Besides this version, the IEnumerable<> interface inherits another version of the GetEnumerator() method from IEnumerable. Its signature is:

abstract GetEnumerator : unit -> IEnumerator

The IEnumerator.GetEnumerator() method returns an IEnumerator (non-generic) value. Since these two methods have the same name, to distinguish them, you must qualify the version of the IEnumerator interface.

Using a Loop

After implementing the IEnumerator<> and the IEnumerable<> interfaces, you can then use the for object in loop. To start, you must prepare the collection and its items for processing. To enumerate the collection, declare a variable based on the implementer of the IEnumerable<> and pass the collection to its constructor. Once this is done, you can then use the loop.

Introduction to Built-In Collection Classes

 

Overview

To support the creation of any kinds of list, the Microsoft .NET Framework provides the ArrayList and the generic List classes. The ArrayList class is defined in the System.Collections namespace while the generic List class is defined in the System.Collections.Generic namespace. Therefore, in order to use one of these classes in your application, make sure you add its namespace in your code file.

The ArrayList class implements the IList, the ICollection, and the IEnumerable interfaces. The List class implements the generic IList<>, the generic ICollection<>, the generic IEnumerable<>, the IList, the ICollection, and the IEnumerable interfaces.

The ArrayList class starts as follows:

type ArrayList =  
    class 
        interface IList 
        interface ICollection 
        interface IEnumerable 
        interface ICloneable 
    end

The generic List class starts as follows:

type List<'T> =  
    class 
        interface IList<'T>
        interface ICollection<'T>
        interface IEnumerable<'T>
        interface IEnumerable 
        interface IList 
        interface ICollection 
        interface IReadOnlyList<'T>
        interface IReadOnlyCollection<'T>
    end

You can use either the ArrayList or the generic List class to create and manage values for a list. Here is an example of declaring an ArrayList variable:

open System
open System.Collections
open System.Windows.Forms

let exercise : Form = new Form()

let lstNames : ArrayList = new ArrayList()

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

If you decide to use the List<> class, you must specify the class (or the record or the structure) used to hold the value. Here is an example:

type AutoPart = {
    PartNumber : int
    Year       : int
    Make       : string
    Model      : string
    Category   : string
    PartName   : string
    Status     : string
    UnitPrice  : float }

let autoParts = new List<AutoPart>()

The default constructor of each class allows you to create an empty list before adding values to it. If you already have an ICollection-based list, that is, a list created from a class that implements the ICollection interface, you can initialize your ArrayList list with it. To support this, the ArrayList class is equipped with the following constructor:

new :
    c : ICollection -> ArrayList

Here is an example:

open System
open System.Drawing
open System.Collections
open System.Windows.Forms

let exercise : Form = new Form(MaximizeBox = false, Text = "Exercise",
                               StartPosition = FormStartPosition.CenterScreen)
let cbxNames : ComboBox = new ComboBox()
cbxNames.Items.Add "Paul Bertrand Yamaguchi" |> ignore
cbxNames.Items.Add "Robert Mukoko" |> ignore
cbxNames.Items.Add "John Gwynne" |> ignore
cbxNames.Items.Add "Bryan Murdock" |> ignore
exercise.Controls.Add cbxNames

let lstNames = new ArrayList(cbxNames.Items)

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

The Capacity of a List

After declaring an ArrayList or a List variable, it may be empty. As objects are added to it, the list grows. The list can grow tremendously as you wish. The number of items of the list is managed through the memory it occupies and this memory grows as needed. The number of items that the memory allocated is currently using is represented by the Capacity property:

abstract Capacity : int with get, set 
override Capacity : int with get, set

The capacity of a list will usually be the least of your concerns. If for some reason, you want to intervene and control the number of items that your list can contain, you can manipulate the Capacity property. For example, you can assign it a value to set the maximum value that the list can contain. Instead of specifying the capacity after the list has been created, when declaring the list variable, you can specify its maximum capacity. To support this, both the ArrayList and the List classes are equipped with an additional constructor as follows:

new : 
    capacity:int -> ArrayList
new : 
    capacity:int -> List

Here is an example of accessing this property for a generic List<>:

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

[<Serializable>]
type AutoPart = {
    PartNumber : int
    Year       : int
    Make       : string
    Model      : string
    Category   : string
    PartName   : string
    Status     : string
    UnitPrice  : float }

let mutable autoParts = new List<AutoPart>()

let collegeParkAutoParts = new Form(StartPosition = FormStartPosition.CenterScreen,
                                    MaximizeBox = false, Text = "College Park Auto-Parts")

// Capacity
collegeParkAutoParts.Controls.Add(new Label(AutoSize = true, Location = new Point(16, 16), Text = "Capacity:"))                             
let txtCapacity = new TextBox(Location = new Point(73, 14), Width = 63)
collegeParkAutoParts.Controls.Add txtCapacity

let showAutoParts() =
    txtCapacity.Text <- sprintf "%i" autoParts.Capacity
    
let collegeParkAutoPartsLoad e =
    showAutoParts()
collegeParkAutoParts.Load.Add collegeParkAutoPartsLoad

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

This would produce:

The Capacity of a List

Once again, you will hardly have any reason to use the Capacity property: the compiler knows what to do with it.

A Read-Only List

Some of the reasons you create a list are to be able to add values to it, edit its values, retrieve a value, or delete values from it. These are the default operations. You can still limit these operations as you judge them unnecessary. For example, you may create a list and then initialize it with the values that you want the list to only have. If you do not want to have the user adding values to it, you can create the list as read-only. To do this, you can call the ArrayList.ReadOnly() method. It is overloaded with two versions as follows:

static member ReadOnly : 
        list:ArrayList -> ArrayList
static member ReadOnly : 
        list:IList -> IList

Some operations cannot be performed on a read-only list. To perform such operations, you can first find out whether an ArrayList list is read-only. This is done by checking its IsReadOnly property:

abstract IsReadOnly : bool with get 
override IsReadOnly : bool with get

To let you produce a read-only collection from an existing generic List<> collection, the List<> class provides a method named AsReadOnly whose signature is:

member AsReadOnly : unit -> ReadOnlyCollection<'T>

Here is an example of calling this method:

open System
open System.Windows.Forms
open System.Collections.Generic
open System.Collections.ObjectModel

[<Serializable>]
type AutoPart = {
    PartNumber : int
    Year       : int
    Make       : string
    Model      : string
    Category   : string
    PartName   : string
    Status     : string
    UnitPrice  : float }

let mutable autoParts = new List<AutoPart>()

let collegeParkAutoParts = new Form(Text = "College Park Auto-Parts")

let showAutoParts() =
    let readOnlyParts : ReadOnlyCollection<AutoPart>  = autoParts.AsReadOnly()
    ()

let collegeParkAutoPartsLoad e =
    showAutoParts()
collegeParkAutoParts.Load.Add collegeParkAutoPartsLoad

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

After doing this, you get a generic ReadOnlyCollection list that you can use as you see fit, using the properties and methods of that class.

Adding Items to a List

 

Adding an Item

The primary operation performed on a list is to create one or more values. To do this, you have various alternatives. Both the ArrayList and the List classes are equipped with an Add() method. The signature of the System.Collections.ArrayList.Add() method is:

abstract Add : 
        value:Object -> int  
override Add : 
        value:Object -> int

The signature of the System.Collections.Generic.List.Add() method is:

abstract Add : 
        item:'T -> unit  
override Add : 
        item:'T -> unit

The argument of the method is the value to add to the list. If the method succeeds with the addition, it returns the position where the value was added in the list. Here are examples for a List variable:

let lstNames : List<string> = new List<string>();

lstNames.Add "Christine Kingston"
lstNames.Add "Hermine Paulson"
lstNames.Add "William Harrison"
lstNames.Add "Ursula Morales"
lstNames.Add "Evan Lancos"

In the same way, you can add an item that is based on a record, a class, or a structure:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic
open System.Collections.ObjectModel
open System.Runtime.Serialization.Formatters.Binary

[<Serializable>]
type AutoPart = {
    PartNumber : int
    Year       : int
    Make       : string
    Model      : string
    Category   : string
    PartName   : string
    Status     : string
    UnitPrice  : float }

let mutable autoParts = new List<AutoPart>()

// ---------------------------------------------------------------------------------
let make = new Form(FormBorderStyle = FormBorderStyle.FixedDialog,
                    ClientSize = new System.Drawing.Size(252, 89),
                    MaximizeBox = false, Text = "College Park Auto-Parts: Make",
                    MinimizeBox = false, StartPosition = FormStartPosition.CenterScreen)

make.Controls.Add(new Label(Location = new Point(19, 18),
                                AutoSize = true, Text = "&Make:"))
let txtMake = new TextBox(Location = new Point(77, 15), Width = 156)
make.Controls.Add txtMake

let btnMakeOK = new Button(DialogResult = DialogResult.OK,
                           Location = new Point(77, 51), Text = "&OK")
make.Controls.Add btnMakeOK
let btnMakeCancel = new Button(DialogResult = DialogResult.Cancel,
                               Location = new Point(158, 51), Text = "&Cancel")
make.Controls.Add btnMakeCancel

make.AcceptButton <- btnMakeOK
make.CancelButton <- btnMakeCancel
// ---------------------------------------------------------------------------------
let model = new Form(FormBorderStyle = FormBorderStyle.FixedDialog,
                     ClientSize = new System.Drawing.Size(252, 89),
                     MaximizeBox = false, Text = "College Park Auto-Parts: Model",
                     MinimizeBox = false, StartPosition = FormStartPosition.CenterScreen)

model.Controls.Add(new Label(Location = new Point(19, 18),
                             AutoSize = true, Text = "&Model:", TabIndex = 0))
let txtModel = new TextBox(Location = new Point(77, 15), Width = 156, TabIndex = 1)
model.Controls.Add txtModel

let btnModelOK = new Button(Location = new Point(77, 51), Text = "&OK",
                            DialogResult = DialogResult.OK, TabIndex = 2)
model.Controls.Add btnModelOK
let btnModelCancel = new Button(Location = new Point(158, 51), Text = "&Cancel",
                                DialogResult = DialogResult.Cancel, TabIndex = 3)
model.Controls.Add btnModelCancel

model.AcceptButton <- btnModelOK
model.CancelButton <- btnModelCancel
// ---------------------------------------------------------------------------------
let category = new Form(MaximizeBox = false, MinimizeBox = false,
                        Text = "College Park Auto-Parts: Category",
                        ClientSize = new System.Drawing.Size(252, 89),
                        FormBorderStyle = FormBorderStyle.FixedDialog,
                        StartPosition = FormStartPosition.CenterScreen)

category.Controls.Add(new Label(AutoSize = true, Text = "C&ategory:",
                                Location = new Point(19, 18), TabIndex = 0))
let txtCategory = new TextBox(Location = new Point(77, 15), Width = 156, TabIndex = 1)
category.Controls.Add txtCategory

let btnCategoryOK = new Button(Location = new Point(77, 51),Text = "&OK",
                               DialogResult = DialogResult.OK, TabIndex = 2)
category.Controls.Add btnCategoryOK

let btnCategoryCancel = new Button(Location = new Point(158, 51), Text = "&Cancel",
                                   DialogResult = DialogResult.Cancel, TabIndex = 3)
category.Controls.Add btnCategoryCancel

category.AcceptButton <- btnCategoryOK
category.CancelButton <- btnCategoryCancel
// ---------------------------------------------------------------------------------
let autoPartNew = new Form(MaximizeBox = false, MinimizeBox = false,
                           FormBorderStyle = FormBorderStyle.FixedDialog,
                           ClientSize = new System.Drawing.Size(360, 225),
                           Text = "College Park Auto-Parts: New Auto Part",
                           StartPosition = FormStartPosition.CenterScreen)
 
// Year
autoPartNew.Controls.Add(new Label(TabIndex = 3, Text = "&Year:",
                                   AutoSize = true, Location = new Point(18, 21)))
let cbxYears = new ComboBox(Location = new Point(85, 18), Width = 65, TabIndex = 4)
autoPartNew.Controls.Add cbxYears

// Make
autoPartNew.Controls.Add(new Label(TabIndex = 5, Text = "M&ake:",
                                   Location = new Point(18, 51), AutoSize = true))
let cbxMakes = new ComboBox(Location = new Point(85, 47), Width = 121, Sorted = true, TabIndex = 6)
autoPartNew.Controls.Add cbxMakes

// Button: New Make
let btnNewMake = new Button(TabIndex = 7, Text = "New &Make...",
                            Location = new Point(212, 47), Width = 92)
autoPartNew.Controls.Add btnNewMake

let btnNewMakeClick e =
    if make.ShowDialog() = DialogResult.OK then
        if String.IsNullOrEmpty(txtMake.Text) = false then
            let strMake = txtMake.Text

            // Make sure the make is not yet in the list
            if cbxMakes.Items.Contains(strMake) then
                MessageBox.Show(strMake + " is already in the list.",
                                "College Park Auto-Parts",
                                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
            else
                // Since this is a new make, add it to the combo box
                cbxMakes.Items.Add(strMake) |> ignore

            cbxMakes.Text <- strMake
        txtMake.Text <- ""
btnNewMake.Click.Add btnNewMakeClick

// Model
autoPartNew.Controls.Add(new Label(AutoSize = true, Text = "M&odel:",
                                   Location = new Point(18, 77), TabIndex = 8))
let cbxModels = new ComboBox(Location = new Point(85, 74),
                             Sorted = true, TabIndex = 9, Width = 121)
autoPartNew.Controls.Add cbxModels

let cbxMakesSelectedIndexChanged e =
    cbxModels.Text <- ""
    cbxModels.Items.Clear()

    for part in autoParts do
        if part.Make = cbxMakes.Text then
            if cbxModels.Items.Contains(part.Model) = false then
                cbxModels.Items.Add(part.Model) |> ignore
cbxMakes.SelectedIndexChanged.Add cbxMakesSelectedIndexChanged

let btnNewModel = new Button(TabIndex = 10, Text = "New Mo&del...",
                             Location = new Point(212, 72), Width = 92)
autoPartNew.Controls.Add btnNewModel

let btnNewModelClick e =
    if model.ShowDialog() = DialogResult.OK then
        if String.IsNullOrEmpty(txtModel.Text) = false then
            let strModel = txtModel.Text

            // Make sure the model is not yet in the list
            if cbxModels.Items.Contains(strModel) then
                MessageBox.Show(strModel + " is already in the Models combo box.",
                                "College Park Auto-Parts",
                                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
            else
                // Since this is a new model, add it to the combo box
                cbxModels.Items.Add(strModel) |> ignore

            cbxModels.Text <- strModel
        txtModel.Text <- ""
btnNewModel.Click.Add btnNewModelClick

// Category
autoPartNew.Controls.Add(new Label(AutoSize = true, Text = "&Category:",
                                   Location = new Point(18, 104), TabIndex = 11))
let cbxCategories = new ComboBox(Location = new Point(85, 101),
                                 Width = 121, Sorted = true, TabIndex = 12)
autoPartNew.Controls.Add cbxCategories
let btnNewCategory = new Button(TabIndex = 13, Text = "New Ca&tegory...",
                                Location = new Point(212, 99), Width = 92)
autoPartNew.Controls.Add btnNewCategory;

let btnNewCategoryClick e =
    if category.ShowDialog() = DialogResult.OK then
        if String.IsNullOrEmpty(txtCategory.Text) = false then
            let strCategory = txtCategory.Text

            // Make sure the category is not yet in the list
            if cbxCategories.Items.Contains(strCategory) then
                MessageBox.Show(strCategory + " is already in the Categories combo box.",
                                "College Park Auto-Parts",
                                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
            else
                // Since this is a new category, add it to the combo box
                cbxCategories.Items.Add(strCategory) |> ignore

            cbxCategories.Text <- strCategory
        txtCategory.Text <- ""
btnNewCategory.Click.Add btnNewCategoryClick

// Part Name
autoPartNew.Controls.Add(new Label(AutoSize = true, Text = "&Part Name:",
                                   Location = new Point(18, 131), TabIndex = 14))
let txtPartName = new TextBox(Location = new Point(85, 128), Width = 255, TabIndex = 15)
autoPartNew.Controls.Add txtPartName

// Status
autoPartNew.Controls.Add(new Label(TabIndex = 16, Text = "&Status:",
                                   Location = new Point(18, 156), AutoSize = true))
let cbxPartsStatus = new ComboBox(Location = new Point(85, 153), Width = 121, TabIndex = 17);
cbxPartsStatus.Items.AddRange [| "New"; "Sold"; "Used"; "Damaged"; "Refurbished"; "Unknown" |]
autoPartNew.Controls.Add cbxPartsStatus

// Unit Price
autoPartNew.Controls.Add(new Label(AutoSize = true, Text = "&Unit Price:",
                                   Location = new Point(212, 157), TabIndex = 18))
let txtUnitPrice = new TextBox(Text = "0.00", TextAlign = HorizontalAlignment.Right,
                               Location = new Point(275, 154), Width = 65, TabIndex = 19)
autoPartNew.Controls.Add txtUnitPrice

// Part Number
autoPartNew.Controls.Add(new Label(Location = new Point(18, 183), TabIndex = 0,
                                   AutoSize = true, Text = "Part #:", Width = 65))

let txtPartNumber = new TextBox(Location = new Point(85, 180), Width = 65, TabIndex = 1)
autoPartNew.Controls.Add txtPartNumber

let autoPartNewLoad e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()

    for i = DateTime.Today.Year + 1 downto 1960 do
        cbxYears.Items.Add(sprintf "%i" i) |> ignore

    // Create a random number that will be used to identify the item
    let rnd : Random = new Random()
    txtPartNumber.Text <- sprintf "%i" (rnd.Next(100000, 999999))
    
    // This is the file that holds the list of parts
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs";

    if File.Exists(strAutoPartsFile) then
        use fsAutoParts = new FileStream(strAutoPartsFile,
                                         FileMode.Open, FileAccess.Read, FileShare.Read)
        // Retrieve the list of items from file
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>

        // Display the car manufacturers in the Make combo box
        for part in autoParts do
            if cbxMakes.Items.Contains(part.Make) = false then
                cbxMakes.Items.Add(part.Make) |> ignore

        // Display the pats categories in the Category combo box
        for part in autoParts do
            if cbxCategories.Items.Contains(part.Category) = false then
                cbxCategories.Items.Add(part.Category) |> ignore
autoPartNew.Load.Add autoPartNewLoad

// Button: Submit
let btnSubmit = new Button(Location = new Point(184, 190),
                           Width = 75, TabIndex = 20, Text = "&Submit")
autoPartNew.Controls.Add btnSubmit

let btnSubmitClick e =
    let bfAutoParts = new BinaryFormatter()

    Directory.CreateDirectory(@"C:\College Park Auto Parts") |> ignore

    // This is the file that holds the list of items
    let strAutoPartsFile : string =  @"C:\College Park Auto Parts\AutoParts.prs"

    if File.Exists(strAutoPartsFile) then
        use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        fsAutoParts.Close()

    let part = {
        PartNumber = int txtPartNumber.Text
        Year       = int cbxYears.Text
        Make       = cbxMakes.Text
        Model      = cbxModels.Text
        Category   = cbxCategories.Text
        PartName   = txtPartName.Text
        Status     = cbxPartsStatus.Text
        UnitPrice  = float txtUnitPrice.Text }

    autoParts.Add part

    use fsAutoParts = new FileStream(strAutoPartsFile,
                                     FileMode.Create, FileAccess.Write, FileShare.Write)
    bfAutoParts.Serialize(fsAutoParts, autoParts)
    fsAutoParts.Close()

    // After saving the item, reset the form
    cbxYears.Text       <- ""
    cbxMakes.Text       <- ""
    cbxModels.Text      <- ""
    cbxCategories.Text  <- ""
    txtPartName.Text    <- ""
    cbxPartsStatus.Text <- "New"
    txtUnitPrice.Text   <- "0.00"
    let rndNumber : Random = new Random()
    txtPartNumber.Text  <- sprintf "%i" (rndNumber.Next(100000, 999999))

btnSubmit.Click.Add btnSubmitClick

// Button: Close
let btnAutoPartNewClose = new Button(Width = 75, TabIndex = 21, Text = "Close",
                                     DialogResult = DialogResult.Cancel, Location = new Point(265, 190))
autoPartNew.Controls.Add btnAutoPartNewClose

let btnAutoPartNewCloseClick e = autoPartNew.Close()
btnAutoPartNewClose.Click.Add btnAutoPartNewCloseClick
// ---------------------------------------------------------------------------------
let collegeParkAutoParts = new Form(ClientSize = new System.Drawing.Size(746, 476),
                                    StartPosition = FormStartPosition.CenterScreen,
                                    MaximizeBox = false, Text = "College Park Auto-Parts")
// List View Columns
let colPartNumber = new ColumnHeader(Text = "Part #")
let colYear = new ColumnHeader(Text = "Year", TextAlign = HorizontalAlignment.Center, Width = 40)
let colMake = new ColumnHeader(Text = "Make", Width = 65)
let colModel = new ColumnHeader(Text = "Model", Width = 70)
let colCategory = new ColumnHeader(Text = "Category", Width = 80)
let colPartName = new ColumnHeader(Text = "Part Name", Width = 250)
let colStatus = new ColumnHeader(Text = "Status")
let colUnitPrice = new ColumnHeader(Text = "Unit Price", TextAlign = HorizontalAlignment.Right)

// List View: Auto-Parts
let lvwAutoParts = new ListView(FullRowSelect = true, GridLines = true,
                                View = System.Windows.Forms.View.Details,
                                Location = new Point(14, 17), Size = new System.Drawing.Size(712, 362))
lvwAutoParts.Anchor <- AnchorStyles.Top ||| AnchorStyles.Bottom ||| AnchorStyles.Left ||| AnchorStyles.Right
lvwAutoParts.Columns.AddRange [| colPartNumber; colYear; colMake; colModel; colCategory; colPartName; colStatus; colUnitPrice|]
collegeParkAutoParts.Controls.Add lvwAutoParts

// Capacity
collegeParkAutoParts.Controls.Add(new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left),
                                            AutoSize = true, Location = new Point(16, 398), Text = "Capacity:"))                             
let txtCapacity = new TextBox(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Location = new Point(73, 396), Width = 63)
collegeParkAutoParts.Controls.Add txtCapacity

let showAutoParts() =
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()

    if File.Exists(strAutoPartsFile) then
        lvwAutoParts.Items.Clear();

        let fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
        // Retrieve the list of parts from file
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        fsAutoParts.Close()

        let readOnlyParts : ReadOnlyCollection<AutoPart>  = autoParts.AsReadOnly()
        for part in readOnlyParts do
            let lviAutoPart = new ListViewItem(sprintf "%i" part.PartNumber)
            lviAutoPart.SubItems.Add(sprintf "%i" part.Year) |> ignore
            lviAutoPart.SubItems.Add(part.Make) |> ignore
            lviAutoPart.SubItems.Add(part.Model) |> ignore
            lviAutoPart.SubItems.Add(part.Category) |> ignore
            lviAutoPart.SubItems.Add(part.PartName) |> ignore
            lviAutoPart.SubItems.Add(part.Status) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%0.02f" part.UnitPrice) |> ignore
            lvwAutoParts.Items.Add lviAutoPart
            
    txtCapacity.Text <- sprintf "%i" autoParts.Capacity

let collegeParkAutoPartsLoad e =
    showAutoParts()
collegeParkAutoParts.Load.Add collegeParkAutoPartsLoad

// Button: New Auto-Part
let btnNewAutoPart = new Button(Location = new Point(14, 432), Size = new System.Drawing.Size(210, 34),
                                Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Text = "New Au&to Part...")
collegeParkAutoParts.Controls.Add btnNewAutoPart

let btnNewAutoPartClick e =
    autoPartNew.ShowDialog() |> ignore
    showAutoParts()
btnNewAutoPart.Click.Add btnNewAutoPartClick

// Button: Close the Application
let btnApplicationClose = new Button(Location = new Point(498, 432), Text = "Close Application",
                                     Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Size = new System.Drawing.Size(210, 34))

let btnApplicationCloseClick _ =
    collegeParkAutoParts.Close()
btnApplicationClose.Click.Add btnApplicationCloseClick
collegeParkAutoParts.Controls.Add btnApplicationClose

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

This would produce:

College Park Auto Parts

If the method fails to add the value and if you are using the ArrayList class, the compiler would throw an error. One of the errors that could result from the ArrayList's failure of this operation would be based on the fact that either a new value cannot be added to the list because the list is read-only, or the list was already full prior to adding the new value. Normally, a list can be full only if you had specified the maximum number of items it can contain using the ArrayList.Capacity property. As mentioned above, the list can be made read-only by passing its variable to the ArrayList.ReadOnly() method.

Adding a Range of Items

Instead of adding one value at a time, you can first create a list of values and add that whole list at once. To support this operation, both the ArrayList and the List classes are equipped with a method named AddRange.

The signature of the ArrayList.AddRange() method is:

abstract AddRange : 
        c:ICollection -> unit  
override AddRange : 
        c:ICollection -> unit

The signature of the List.AddRange() method is:

member AddRange :
        collection:IEnumerable<'T> -> unit

The ArrayList.AddRange() method takes as argument a list created from a class that implements the ICollection interface. Here is an example:

open System
open System.Drawing
open System.Collections
open System.Windows.Forms

let exercise = new Form(ClientSize = new System.Drawing.Size(232, 238),
                                   MaximizeBox = false, Text = "Employees")

let cbxNames : ComboBox = new ComboBox(Location = new Point(12, 12))

cbxNames.Items.Add "Christine Kingston" |> ignore
cbxNames.Items.Add "Hermine Paulson" |> ignore
cbxNames.Items.Add "William Harrison" |> ignore
cbxNames.Items.Add "Ursula Morales" |> ignore
exercise.Controls.Add cbxNames

let lstNames = new ArrayList()
lstNames.AddRange cbxNames.Items

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

The List<>.AddRange() method takes as argument a list created from a class that implements the generic IEnumerable interface such as an array. Here is an example:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic
open System.Runtime.Serialization.Formatters.Binary

[<Serializable>]
type AutoPart = { PartNumber : int; Year : int; Make : string; Model : string; Category : string; PartName : string; Status : string; UnitPrice : float }

let mutable autoParts = new List<AutoPart>()
// ---------------------------------------------------------------------------------
let collegeParkAutoParts = new Form(ClientSize = new System.Drawing.Size(245, 80),
                                    StartPosition = FormStartPosition.CenterScreen,
                                    MaximizeBox = false, Text = "College Park Auto-Parts")

// Button: Add Range
let btnAddRange = new Button(Location = new Point(17, 17), Text = "Add Range",
                             Size = new System.Drawing.Size(210, 43))
let btnAddRangeClick e =
    let bfmAutoParts : BinaryFormatter = new BinaryFormatter()
    // Create a random number that will be used to identify the item
    let rndNumber : Random  = new Random();
    let strFileName  : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if File.Exists(strFileName) then
        use fsAutoParts = new FileStream(strFileName, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfmAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
    	fsAutoParts.Close()

    autoParts.AddRange [| { PartNumber = rndNumber.Next(100000, 999999); Year = 2012; Make = "Chevrolet"; Model = "Malibu 2.4L L4"; Category = "Brake System"; PartName = "Brake Pads"; Status = "New"; UnitPrice = 24.25 }
                          { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Toyota";    Model = "Corolla 1.8L L4"; Category = "Exhaust & Emissions"; PartName = "Shepherd Auto Parts 2-1/4\" Weld On Stainless Steel 1 Slanted Round Exhaust Muffler"; Status = "New"; UnitPrice = 25.95 }
                          { PartNumber = rndNumber.Next(100000, 999999); Year = 2014; Make = "Dodge";     Model = "Charger 3.6L V6 Gas AWD"; Category = "Brake System"; PartName = "StopTech 309.10530 Street Performance Rear Brake Pad"; Status = "Used"; UnitPrice = 62.50 }
                          { PartNumber = rndNumber.Next(100000, 999999); Year = 2010; Make = "Chevrolet"; Model = "Malibu 2.4L L4 Gas"; Category = "Air Filters"; PartName = "Fram CA9492 Extra Guard Flexible Panel Air Filter"; Status = "Damaged"; UnitPrice = 9.25 }
                          { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Toyota";    Model = "Corolla 1.8L L4"; Category = "Exhaust & Emissions"; PartName = "Shepherd Auto Parts 2-1/4\" Weld On Stainless Steel 2 Slanted Round Exhaust Muffler"; Status = "Damaged"; UnitPrice = 45.55 }
                          { PartNumber = rndNumber.Next(100000, 999999); Year = 2014; Make = "Dodge";     Model = "Charger 3.6L V6 Gas AWD"; Category = "Brake System"; PartName = "Power Stop K1719 Rear Ceramic Brake Pad and Cross Drilled/Slotted Combo Rotor One-Click Brake Kit"; Status = "New"; UnitPrice = 142.95 } |]

    use fsAutoParts = new FileStream(strFileName, FileMode.Create, FileAccess.Write, FileShare.Write)
    bfmAutoParts.Serialize(fsAutoParts, autoParts)
    fsAutoParts.Close()

    MessageBox.Show("A range of auto parts has been added to the inventory.",
                    "College Park Auto-Parts",
                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
btnAddRange.Click.Add btnAddRangeClick
collegeParkAutoParts.Controls.Add btnAddRange

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

Inserting an Item

The Add() method allows you to add a new item at the end of the list. An alternative is to add the new item anywhere inside the list. To support this operation, both the System.Collections.ArrayList and the System.Collections.Generic.List<> classes are equipped with a method named Insert. The signature for the ArrayList is:

abstract Insert : 
        index:int * 
        value:Object -> unit  
override Insert : 
        index:int * 
        value:Object -> unit

The signature for the generic List class is:

abstract Insert : 
        index:int * 
        item:'T -> unit  
override Insert : 
        index:int * 
        item:'T -> unit

The method takes two arguments. The first is the position where the new item should be positioned. The second argument is the item to be added. Here is an example:

let names : List<string> = new List<string>();

names.Add "Christine Kingston"
names.Add "Hermine Paulson"
names.Add "William Harrison"
names.Add "Ursula Morales"
names.Add "Evan Lancos"

names.Insert(2, "Arlene Guthrie")

Inserting a Range of Items

You can also insert a range of items. This operation is possible through a method named InsertRange. The signature for the ArrayList class is:

abstract InsertRange : 
        index:int * 
        c:ICollection -> unit  
override InsertRange : 
        index:int * 
        c:ICollection -> unit

The signature for the generic List<> class is:

member InsertRange : 
        index:int * 
        collection:IEnumerable<'T> -> unit

Here is an example of calling this method:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic
open System.Runtime.Serialization.Formatters.Binary

[<Serializable>]
type AutoPart = { PartNumber : int; Year : int; Make : string; Model : string; Category : string; PartName : string; Status : string; UnitPrice : float }

let mutable autoParts = new List<AutoPart>()
// ---------------------------------------------------------------------------------
let collegeParkAutoParts = new Form(ClientSize = new System.Drawing.Size(245, 80),
                                    StartPosition = FormStartPosition.CenterScreen,
                                    MaximizeBox = false, Text = "College Park Auto-Parts")

// Button: Insert Range
let btnInsertRange = new Button(Size = new System.Drawing.Size(210, 43), 
                                Location = new Point(17, 17), Text = "Insert Range")

let btnInsertRangeClick e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strFileName : string = @"C:\College Park Auto Parts\AutoParts.prs"

    let rndNumber : Random = new Random()

    if File.Exists(strFileName) then
        use fsAutoParts = new FileStream(strFileName, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
	fsAutoParts.Close()

        autoParts.InsertRange(5, [| { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Toyota"; Model = "Camry 2.5L L4"; Category = "Steering System"; PartName = "Beck Arnley 103-3068 Steering Rack Boot Kit"; Status = "Used"; UnitPrice = 9.55 }
                                    { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Nissan"; Model = "Sentra SE-R 2.5L L4"; Category = "Struts & Suspension"; PartName = "Monroe 72378 OESpectrum Sensa-Trac Front Strut Assembly"; Status = "New"; UnitPrice = 59.95 }
                                    { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Toyota"; Model = "Camry 2.5L L4"; Category = "Struts & Suspension"; PartName = "NGK 4469 Spark Plug"; Status = "Damaged"; UnitPrice = 59.95 }
                                    { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Toyota"; Model = "Camry 2.5L L4"; Category = "Air Filters"; PartName = "FRAM CF10285 Fresh Breeze Cabin Air Filter"; Status = "New"; UnitPrice = 12.25 } |])

        use fsAutoParts = new FileStream(strFileName, FileMode.Create, FileAccess.Write, FileShare.Write)
        bfAutoParts.Serialize(fsAutoParts, autoParts)
	fsAutoParts.Close()
        
        MessageBox.Show("A range of auto parts has been inserted into the inventory.",
                        "College Park Auto-Parts",
                        MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
        
btnInsertRange.Click.Add btnInsertRangeClick
collegeParkAutoParts.Controls.Add btnInsertRange

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

The Number of Items in the List

When using a list, at any time, you should be able to know the number of items that the list contains. This information is provided by the ArrayList.Count or the List.Count property:

abstract Count : int with get 
override Count : int with get

Here is an example:

let showAutoParts() =
    txtCount.Text    <- sprintf "%i" autoParts.Count

The Capacity and the Count properties have this in common: the value of each increases as the list grows and the same value decreases if the list shrinks. It is important to know that there are various differences between the capacity of a list and the number of items it contains. Capacity is a read/write property. This means that you can assign a value to the capacity to fix the number of items that the list can contain. You can also retrieve the value of the Capacity. The Count property is read-only because it is used by the compiler to count the current number of values of the list and this counting is performed without your intervention.

Getting Items from a List

 

Retrieving an Item from the List

Once a list is ready, you can perform different types of operations on it. Besides adding items, one of the most regular operations performed on a list consists of locating and retrieving its values. You have various options.

To give you access to each member of their list, both the ArrayList and the List classes are equipped with the default Item property:

// ArrayList
abstract Item :
        index:int -> Object with get, set
override Item :
        index:int -> Object with get, set

// List
abstract Item :
        index:int -> 'T with get, set
override Item :
        index:int -> 'T with get, set

The Item property is an indexer. The first value of the list has an index of 0. The second has an index of 1, and so on. 

To retrieve a single value based on its position, you can apply the square brackets of arrays to the variable. Here is an example:

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

let exercise = new Form(ClientSize = new System.Drawing.Size(148, 128),
                                   MaximizeBox = false, Text = "Employees")

let lstNames : List<string> = new List<string>()

lstNames.Add "Christine Kingston"
lstNames.Add "Hermine Paulson"
lstNames.Add "William Harrison"
lstNames.Add "Ursula Morales"
lstNames.Add "Evan Lancos"

let lbxNames : ListBox = new ListBox(Location = new Point(12, 12))
exercise.Controls.Add lbxNames

for n in 0 .. lstNames.Count - 1 do
    lbxNames.Items.Add(sprintf "%s" lstNames.[n]) |> ignore

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

Retrieving an Item From a List

Another issue to keep in mind is that the ArrayList.[] indexer returns an Object value. Therefore, you may have to cast this value to your type of value to get it right.

Besides using the index to access a value from the list, the ArrayList and the List classes implement the IEnumerable.GetEnumerator() method. For this reason, you can use a for .. in loop to access each member of the collection. Here is an example:

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

let exercise = new Form(ClientSize = new System.Drawing.Size(148, 128),
                                   MaximizeBox = false, Text = "Employees")

let lstNames : List<string> = new List<string>()

lstNames.Add "Christine Kingston"
lstNames.Add "Hermine Paulson"
lstNames.Add "William Harrison"
lstNames.Add "Ursula Morales"
lstNames.Add "Evan Lancos"

let lbxNames : ListBox = new ListBox(Location = new Point(12, 12))
exercise.Controls.Add lbxNames

for name in lstNames do
    lbxNames.Items.Add(sprintf "%s" name) |> ignore

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

You can use the Item property to change a value in the list. Because the Item property is used to access an existing value from the list, the value must have been created. If you try setting the value of a non existing item, the compiler would throw an ArgumentOutOfRangeException Exception.

For Each Item in the List

To allow you to access each item in the list, the List class is equipped with a method named ForEach. Its signature is:

member ForEach : 
        action:Action<'T> -> unit

Here is an example of  calling this method:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic
open System.Runtime.Serialization.Formatters.Binary

[<Serializable>]
type AutoPart = {
    PartNumber : int
    Year       : int
    Make       : string
    Model      : string
    Category   : string
    PartName   : string
    Status     : string
    UnitPrice  : float }

let mutable autoParts = new List<AutoPart>()
// ---------------------------------------------------------------------------------
let collegeParkAutoParts = new Form(ClientSize = new System.Drawing.Size(746, 462),
                                    StartPosition = FormStartPosition.CenterScreen,
                                    MaximizeBox = false, Text = "College Park Auto-Parts")
// List View Columns
let colPartNumber = new ColumnHeader(Text = "Part #")
let colYear = new ColumnHeader(Text = "Year", TextAlign = HorizontalAlignment.Center, Width = 40)
let colMake = new ColumnHeader(Text = "Make", Width = 65)
let colModel = new ColumnHeader(Text = "Model", Width = 70)
let colCategory = new ColumnHeader(Text = "Category", Width = 80)
let colPartName = new ColumnHeader(Text = "Part Name", Width = 250)
let colStatus = new ColumnHeader(Text = "Status")
let colUnitPrice = new ColumnHeader(Text = "Unit Price", TextAlign = HorizontalAlignment.Right)

// List View: Auto-Parts
let lvwAutoParts = new ListView(FullRowSelect = true, GridLines = true,
                                View = System.Windows.Forms.View.Details,
                                Location = new Point(14, 17), Size = new System.Drawing.Size(712, 362))
lvwAutoParts.Anchor <- AnchorStyles.Top ||| AnchorStyles.Bottom ||| AnchorStyles.Left ||| AnchorStyles.Right
lvwAutoParts.Columns.AddRange [| colPartNumber; colYear; colMake; colModel; colCategory; colPartName; colStatus; colUnitPrice|]
collegeParkAutoParts.Controls.Add lvwAutoParts

// Capacity
collegeParkAutoParts.Controls.Add(new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left),
                                            AutoSize = true, Location = new Point(16, 398), Text = "Capacity:"))                             
let txtCapacity = new TextBox(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Location = new Point(73, 396), Width = 63)
collegeParkAutoParts.Controls.Add txtCapacity

// Count
collegeParkAutoParts.Controls.Add(new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left),
                                            AutoSize = true, Location = new Point(16, 427), Text = "Inventory:"))
let txtCount = new TextBox(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Location = new Point(73, 424), Width = 63)
collegeParkAutoParts.Controls.Add txtCount
collegeParkAutoParts.Controls.Add(new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left),
                                            AutoSize = true, Location = new Point(142, 427), Text = "Auto Parts"))

let showAutoParts() =
    let bfAutoParts : BinaryFormatter  = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if File.Exists(strAutoPartsFile) then
        lvwAutoParts.Items.Clear();

        use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)

        // Retrieve the list of parts from file
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
	fsAutoParts.Close()

        autoParts.ForEach(fun (part : AutoPart) ->
            let lviAutoPart : ListViewItem  = new ListViewItem(sprintf "%i" part.PartNumber)

            lviAutoPart.SubItems.Add(sprintf "%i" part.Year) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Make) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Model) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Category) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.PartName) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Status) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%0.02f" part.UnitPrice) |> ignore
            lvwAutoParts.Items.Add lviAutoPart |> ignore )
              
    txtCapacity.Text <- sprintf "%i" autoParts.Capacity
    txtCount.Text    <- sprintf "%i" autoParts.Count

let collegeParkAutoPartsLoad e =
    showAutoParts()
collegeParkAutoParts.Load.Add collegeParkAutoPartsLoad

// Button: Close the Application
let btnApplicationClose = new Button(Location = new Point(498, 412), Text = "Close Application",
                                     Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Size = new System.Drawing.Size(210, 34))

let btnApplicationCloseClick _ =
    collegeParkAutoParts.Close()
btnApplicationClose.Click.Add btnApplicationCloseClick
collegeParkAutoParts.Controls.Add btnApplicationClose

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

This would produce:

College Park Auto Parts

Sorting the Items of a List

Some operations performed on a list require that the items be sorted. Sometimes too, for some reason, you may want to re-arrange the items of a collection. To support this operation, the List<> is equipped with a method named Sort. It is overloaded with four different versions. One of the versions uses the following signature:

member Sort : unit -> unit

Here is an example:

let btnEditorNewMakeClick e =
    let bfAutoParts : BinaryFormatter  = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if make.ShowDialog() = DialogResult.OK then
        
        if String.IsNullOrEmpty(txtMake.Text) = false then
            let strMake = txtMake.Text

            if File.Exists(strAutoPartsFile) then
                use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
                autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
		fsAutoParts.Close()
                        
                let makes : List<string> = new List<string>()

                autoParts.ForEach(fun (part : AutoPart) -> makes.Add(part.Make))

                makes.Sort()

btnEditorMake.Click.Add btnEditorNewMakeClick

The Existense of an Item

 

Checking Whether a List Contains an Item

Instead of the square brackets that allow you to retrieve a value based on its position, you can look for a value based on its complete definition. You have various options. You can first "build" an item and ask the compiler to check whether any item in the list matches your definition. To perform this search, depending on your class, you can call either the ArrayList.Contains() or the List.Contains() method. The signature of the System.Collections.ArrayList.Contains() method is:

abstract Contains : 
        item:Object -> bool  
override Contains : 
        item:Object -> bool

The signature of the System.Collections.Generic.List.Contains() method is:

abstract Contains : 
        item:'T -> bool  
override Contains : 
        item:'T -> bool

The value to look for is passed as argument to the method. The compiler would look for exactly the value, using its definition, in the list. If any detail of the argument fails to match anything about the items in the list, the method would return false. If all characteristics of the argument correspond to a value of the list, the method returns true. Here is an example:

let btnEditorNewModelClick e =
    let bfAutoParts : BinaryFormatter  = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if model.ShowDialog() = DialogResult.OK then
        if String.IsNullOrEmpty(txtModel.Text) = false then
            let strModel = txtModel.Text

            if File.Exists(strAutoPartsFile) then
                use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
                autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        	fsAutoParts.Close()
                        
                let models : List<string> = new List<string>()

                autoParts.ForEach(fun (part : AutoPart) -> models.Add(part.Model))

                models.Sort()

                if models.Contains(strModel) then
                    MessageBox.Show(strModel + " is already in the Models combo box.",
                                    "College Park Auto-Parts",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
            else
                // Since this is a new model, add it to the combo box
                cbxEditorModels.Items.Add(strModel) |> ignore

            cbxEditorModels.Text <- strModel
        txtModel.Text <- ""
btnEditorNewModel.Click.Add btnEditorNewModelClick

Checking Whether an Item Exists in the List

To let you find out whether an item exists in a list, the List class is equipped with the Exists() method. Its signature is:

member Exists : 
        match:Predicate<'T> -> bool

This method takes a function as argument. That function must includes a Boolean operation that applies a condition to each item. If at least one of the items responds positively, the method returns true. Here is an example:

let btnEditorNewCategoryClick e =
    let bfAutoParts : BinaryFormatter  = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if category.ShowDialog() = DialogResult.OK then
        if String.IsNullOrEmpty(txtCategory.Text) = false then
            let strCategory = txtCategory.Text

            if File.Exists(strAutoPartsFile) then
                use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
                autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        	fsAutoParts.Close()

                let categories : List<string> = new List<string>()

                autoParts.ForEach(fun (part : AutoPart) -> categories.Add(part.Category))

                let categoryExists = categories.Exists(fun category -> category = strCategory)

                if categoryExists = true then
                    MessageBox.Show(strCategory + " is already in the list.",
                                    "College Park Auto-Parts",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
                else
                    cbxEditorCategories.Items.Add(strCategory) |> ignore

            cbxEditorCategories.Text <- strCategory
        txtCategory.Text <- ""
btnEditorNewCategory.Click.Add btnEditorNewCategoryClick

Finding an Item in the Collection

If you need to find an object and get it from the collection, you have many options. To let you explicitly find an item, the List<> class is equipped with a primary method named Find. Its signature is:

member Find : 
        match:Predicate<'T> -> 'T

This method takes a function as argument. That function specifies a condition by which to select a particular item. Here is an example:

let btnFindClick e =
    let bfAutoParts = new BinaryFormatter()
    let strAutoPartsFile : string =  @"C:\College Park Auto Parts\AutoParts.prs"

    if File.Exists(strAutoPartsFile) then
        use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        fsAutoParts.Close()
        
    let autoPartExists = autoParts.Exists(fun part -> part.PartNumber = int txtEditorPartNumber.Text)

    if autoPartExists = true then
        let part : AutoPart = autoParts.Find(fun item -> item.PartNumber = int txtEditorPartNumber.Text)
        cbxEditorYears.Text <- sprintf "%d" part.Year
        cbxEditorMakes.Text <- part.Make
        cbxEditorModels.Text <- part.Model
        cbxEditorCategories.Text <- part.Category
        txtEditorPartName.Text <- part.PartName
        cbxEditorPartsStatus.Text <- part.Status
        txtEditorUnitPrice.Text <- sprintf "%0.02f" part.UnitPrice 

btnFind.Click.Add btnFindClick

An alternative is to get the index of a certain item. To let you find this, the List<> class is equipped with a primary method named FindIndex. It is overloaded in three versions. One of the versions uses the following signatures:

member FindIndex : 
        match:Predicate<'T> -> int

Here is an example of calling this method:

let lvwAutoPartsSelectedIndexChanged e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if lvwAutoParts.SelectedItems.Count > 0 then
        let lSelectedNumber = int lvwAutoParts.SelectedItems.[0].Text
 
        if File.Exists(strAutoPartsFile) then
            use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
            autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
            fsAutoParts.Close()

            if File.Exists(strAutoPartsFile) then
                use fsAutoParts : FileStream = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
                autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
	        fsAutoParts.Close()

                txtSelectedIndex.Text <- sprintf "%i" (autoParts.FindIndex(fun part -> part.PartNumber = lSelectedNumber))
lvwAutoParts.SelectedIndexChanged.Add lvwAutoPartsSelectedIndexChanged

The other two versions allow you to specify the starting index of a range of indexes within which to find the item.

Instead of finding and producing one item collection, you can set a condition to find all items items that follow that condition. To let you do this, the  List<> class is equipped with a method named FindAll. Its signature is:

member FindAll :
        match:Predicate<'T> -> List<'T>

This method takes a function that holds a condition to follow. All items that apply that condition are selected and produced in a new list. Here is an example of calling it:

// Finding parts of a manufacturer
collegeParkAutoParts.Controls.Add(new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left),
                                            AutoSize = true, Location = new Point(16, 480), Text = "Find Parts For:"))                             
let txtFindPartsFor = new TextBox(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Location = new Point(138, 478), Width = 63)
collegeParkAutoParts.Controls.Add txtFindPartsFor
let btnFindPartsFor = new Button(Location = new Point(208, 478), Width = 62,
                                 Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Text = "Find")
collegeParkAutoParts.Controls.Add btnFindPartsFor

let btnFindPartsForClick e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if File.Exists(strAutoPartsFile) then
        lvwAutoParts.Items.Clear()

        use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
	fsAutoParts.Close()

        let makeParts : List<AutoPart> = autoParts.FindAll(fun part -> part.Make.ToLower().Contains(txtFindPartsFor.Text.ToLower()))

        if String.IsNullOrEmpty(txtFindPartsFor.Text) = false then
            makeParts.ForEach(fun part ->
            let lviAutoPart : ListViewItem  = new ListViewItem(sprintf "%i" part.PartNumber)

            lviAutoPart.SubItems.Add(sprintf "%i" part.Year) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Make) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Model) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Category) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.PartName) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Status) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%0.02f" part.UnitPrice) |> ignore
            lvwAutoParts.Items.Add lviAutoPart |> ignore )
              
    txtCapacity.Text <- sprintf "%i" autoParts.Capacity
    txtCount.Text    <- sprintf "%i" autoParts.Count

btnFindPartsFor.Click.Add btnFindPartsForClick

Binary Searching for an Item

Another option to look for an item in a list consists of calling the BinarySearch() method of either the ArrayList or the List<> class. It is overloaded in three versions and one of them uses the following signature:

// ArrayList
member BinarySearch : 
        item:'T -> int

// List
abstract BinarySearch : 
        value:Object -> int  
override BinarySearch : 
        value:Object -> int

The value to look for is passed argument to the method. Here is an example:

let btnEditorNewMakeClick e =
    let bfAutoParts : BinaryFormatter  = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if make.ShowDialog() = DialogResult.OK then
        
        if String.IsNullOrEmpty(txtMake.Text) = false then
            let strMake = txtMake.Text

            if File.Exists(strAutoPartsFile) then
                use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
                // Retrieve the list of items from file
                autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
                        
                let makes : List<string> = new List<string>()

                autoParts.ForEach(fun (part : AutoPart) -> makes.Add(part.Make))

                makes.Sort()

                let bs : int = makes.BinarySearch(strMake)

                if bs >= 0 then
                    MessageBox.Show(strMake + " is already in the list at index " + bs.ToString() + ".",
                                    "College Park Auto-Parts",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
                else
                    cbxEditorMakes.Items.Add(strMake) |> ignore

                cbxEditorMakes.Text <- strMake
            txtMake.Text <- ""
btnEditorNewMake.Click.Add btnEditorNewMakeClick
     
 

Deleting Items from a List

 

Deleting an Item

As opposed to adding a value to a list, you may want to remove one. To perform this operation, you have various options. You can ask the compiler to look for an item in the list and if, or once, the compiler finds it, it would delete the value. To perform this type of deletion, you can call the Remove() method of either the ArrayList or the List class. Its signature is:

// ArrayList
abstract Remove : 
        obj:Object -> unit  
override Remove : 
        obj:Object -> unit

// List
abstract Remove : 
        item:'T -> bool  
override Remove : 
        item:'T -> bool

This method accepts as argument the value that you want to delete from the list. To perform this operation, the list must not be read-only. Here is an example:

let btnRemoveAutoPartClick e =
    let mutable lPartNumber = 0
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strAutoPartsFile  : string = @"C:\College Park Auto Parts\AutoParts.prs"
    
    if lvwAutoParts.SelectedItems.Count > 0 then
        lPartNumber <- int (lvwAutoParts.SelectedItems.[0]).Text
        
        if MessageBox.Show("Are you sure you want to delete the selected auto-part?",
                           "College Park Auto-Parts",
                           MessageBoxButtons.YesNo, MessageBoxIcon.Information) = DialogResult.Yes then
        
            if File.Exists(strAutoPartsFile) then
                let fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
                autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
                fsAutoParts.Close()

                let selected : AutoPart = autoParts.Find(fun part -> part.PartNumber = int (lvwAutoParts.SelectedItems.[0]).Text)

                let removed = autoParts.Remove(selected)

                if removed = true then
                    MessageBox.Show("The selected item has been removed from the inventory.",
                                    "College Park Auto-Parts",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
                    // Save the new list
                    use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Create, FileAccess.Write, FileShare.Write)
                    bfAutoParts.Serialize(fsAutoParts, autoParts)
                else
                    MessageBox.Show("For some reason, the item was not removed from the inventory.",
                                    "College Park Auto-Parts",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
    else
        MessageBox.Show("You must first select the item to be removed from the inventory.",
                        "College Park Auto-Parts",
                         MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
    showAutoParts()
btnRemoveAutoPart.Click.Add btnRemoveAutoPartClick

The Remove() method allows you to specify the exact value you want to delete from a list. Another option you have consists of deleting a value based on its position. This is done using the RemoveAt() method whose signature is:

abstract RemoveAt : 
        index:int -> unit  
override RemoveAt : 
        index:int -> unit

With this method, the position of the item is passed as argument. One more option is to specify a criterion by which to select some item(s). Any item that follows that condition would be removed from the collection. To support this operation, the  List<> class is equipped with a method named RemoveAll. Its signature is:

member RemoveAll :
        match:Predicate<'T> -> int

Here is an example:

let btnRemoveDamagedPartsClick e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strAutoPartsFile  : string = @"C:\College Park Auto Parts\AutoParts.prs"
        
    if File.Exists(strAutoPartsFile) then
        let fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        fsAutoParts.Close()
    
        if MessageBox.Show("Are you sure you want to delete all damaged auto-parts?",
                           "College Park Auto-Parts",
                           MessageBoxButtons.YesNo, MessageBoxIcon.Information) = DialogResult.Yes then

            let damagedPartsRemoved = autoParts.RemoveAll(fun part -> part.Status = "Damaged")

            if damagedPartsRemoved > 0 then
                let fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Create, FileAccess.Write, FileShare.Write)
                bfAutoParts.Serialize(fsAutoParts, autoParts)
                fsAutoParts.Close()

                MessageBox.Show("All damaged items have been removed from the inventory.",
                                "College Park Auto-Parts",
                                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
                showAutoParts()
            else
                MessageBox.Show("For some reason, the item was not removed from the inventory.",
                                "College Park Auto-Parts",
                                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
btnRemoveDamagedParts.Click.Add btnRemoveDamagedPartsClick

If the position is not valid because either it is lower or higher than the current Count, the compiler would throw an ArgumentOutOfRangeException exception.

Clearing a List

To remove all items from a list at once, you can call the Clear() method of either the ArrayList or the List class. Its signature is:

abstract Clear : unit -> unit  
override Clear : unit -> unit

Here is an example:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic
open System.Collections.ObjectModel
open System.Runtime.Serialization.Formatters.Binary

[<Serializable>]
type AutoPart = {
    PartNumber        : int
    mutable Year      : int
    mutable Make      : string
    mutable Model     : string
    mutable Category  : string
    mutable PartName  : string
    mutable Status    : string
    mutable UnitPrice : float }

let mutable autoParts = new List<AutoPart>()

// ---------------------------------------------------------------------------------
let make = new Form(FormBorderStyle = FormBorderStyle.FixedDialog,
                    ClientSize = new System.Drawing.Size(252, 89),
                    MaximizeBox = false, Text = "College Park Auto-Parts: Make",
                    MinimizeBox = false, StartPosition = FormStartPosition.CenterScreen)

make.Controls.Add(new Label(Location = new Point(19, 18),
                                AutoSize = true, Text = "&Make:"))
let txtMake = new TextBox(Location = new Point(77, 15), Width = 156)
make.Controls.Add txtMake

let btnMakeOK = new Button(DialogResult = DialogResult.OK,
                           Location = new Point(77, 51), Text = "&OK")
make.Controls.Add btnMakeOK
let btnMakeCancel = new Button(DialogResult = DialogResult.Cancel,
                               Location = new Point(158, 51), Text = "&Cancel")
make.Controls.Add btnMakeCancel

make.AcceptButton <- btnMakeOK
make.CancelButton <- btnMakeCancel
// ---------------------------------------------------------------------------------
let model = new Form(FormBorderStyle = FormBorderStyle.FixedDialog,
                     ClientSize = new System.Drawing.Size(252, 89),
                     MaximizeBox = false, Text = "College Park Auto-Parts: Model",
                     MinimizeBox = false, StartPosition = FormStartPosition.CenterScreen)

model.Controls.Add(new Label(Location = new Point(19, 18),
                             AutoSize = true, Text = "&Model:", TabIndex = 0))
let txtModel = new TextBox(Location = new Point(77, 15), Width = 156, TabIndex = 1)
model.Controls.Add txtModel

let btnModelOK = new Button(Location = new Point(77, 51), Text = "&OK",
                            DialogResult = DialogResult.OK, TabIndex = 2)
model.Controls.Add btnModelOK
let btnModelCancel = new Button(Location = new Point(158, 51), Text = "&Cancel",
                                DialogResult = DialogResult.Cancel, TabIndex = 3)
model.Controls.Add btnModelCancel

model.AcceptButton <- btnModelOK
model.CancelButton <- btnModelCancel
// ---------------------------------------------------------------------------------
let category = new Form(MaximizeBox = false, MinimizeBox = false,
                        Text = "College Park Auto-Parts: Category",
                        ClientSize = new System.Drawing.Size(252, 89),
                        FormBorderStyle = FormBorderStyle.FixedDialog,
                        StartPosition = FormStartPosition.CenterScreen)

category.Controls.Add(new Label(AutoSize = true, Text = "C&ategory:",
                                Location = new Point(19, 18), TabIndex = 0))
let txtCategory = new TextBox(Location = new Point(77, 15), Width = 156, TabIndex = 1)
category.Controls.Add txtCategory

let btnCategoryOK = new Button(Location = new Point(77, 51),Text = "&OK",
                               DialogResult = DialogResult.OK, TabIndex = 2)
category.Controls.Add btnCategoryOK

let btnCategoryCancel = new Button(Location = new Point(158, 51), Text = "&Cancel",
                                   DialogResult = DialogResult.Cancel, TabIndex = 3)
category.Controls.Add btnCategoryCancel

category.AcceptButton <- btnCategoryOK
category.CancelButton <- btnCategoryCancel
// ---------------------------------------------------------------------------------
let autoPartNew = new Form(MaximizeBox = false, MinimizeBox = false,
                           FormBorderStyle = FormBorderStyle.FixedDialog,
                           ClientSize = new System.Drawing.Size(360, 225),
                           Text = "College Park Auto-Parts: New Auto Part",
                           StartPosition = FormStartPosition.CenterScreen)
 
// Year
autoPartNew.Controls.Add(new Label(TabIndex = 3, Text = "&Year:",
                                   AutoSize = true, Location = new Point(18, 21)))
let cbxYears = new ComboBox(Location = new Point(85, 18), Width = 65, TabIndex = 4)
autoPartNew.Controls.Add cbxYears

// Make
autoPartNew.Controls.Add(new Label(TabIndex = 5, Text = "M&ake:",
                                   Location = new Point(18, 51), AutoSize = true))
let cbxMakes = new ComboBox(Location = new Point(85, 47), Width = 121, Sorted = true, TabIndex = 6)
autoPartNew.Controls.Add cbxMakes

// Button: New Make
let btnNewMake = new Button(TabIndex = 7, Text = "New &Make...",
                            Location = new Point(212, 47), Width = 92)
autoPartNew.Controls.Add btnNewMake

let btnNewMakeClick e =
    if make.ShowDialog() = DialogResult.OK then
        if String.IsNullOrEmpty(txtMake.Text) = false then
            let strMake = txtMake.Text

            // Make sure the make is not yet in the list
            if cbxMakes.Items.Contains(strMake) then
                MessageBox.Show(strMake + " is already in the list.",
                                "College Park Auto-Parts",
                                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
            else
                // Since this is a new make, add it to the combo box
                cbxMakes.Items.Add(strMake) |> ignore

            cbxMakes.Text <- strMake
        txtMake.Text <- ""
btnNewMake.Click.Add btnNewMakeClick

// Model
autoPartNew.Controls.Add(new Label(AutoSize = true, Text = "M&odel:",
                                   Location = new Point(18, 77), TabIndex = 8))
let cbxModels = new ComboBox(Location = new Point(85, 74),
                             Sorted = true, TabIndex = 9, Width = 121)
autoPartNew.Controls.Add cbxModels

let cbxMakesSelectedIndexChanged e =
    cbxModels.Text <- ""
    cbxModels.Items.Clear()

    for part in autoParts do
        if part.Make = cbxMakes.Text then
            if cbxModels.Items.Contains(part.Model) = false then
                cbxModels.Items.Add(part.Model) |> ignore
cbxMakes.SelectedIndexChanged.Add cbxMakesSelectedIndexChanged

let btnNewModel = new Button(TabIndex = 10, Text = "New Mo&del...",
                             Location = new Point(212, 72), Width = 92)
autoPartNew.Controls.Add btnNewModel

let btnNewModelClick e =
    if model.ShowDialog() = DialogResult.OK then
        if String.IsNullOrEmpty(txtModel.Text) = false then
            let strModel = txtModel.Text

            // Make sure the model is not yet in the list
            if cbxModels.Items.Contains(strModel) then
                MessageBox.Show(strModel + " is already in the Models combo box.",
                                "College Park Auto-Parts",
                                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
            else
                // Since this is a new model, add it to the combo box
                cbxModels.Items.Add(strModel) |> ignore

            cbxModels.Text <- strModel
        txtModel.Text <- ""
btnNewModel.Click.Add btnNewModelClick

// Category
autoPartNew.Controls.Add(new Label(AutoSize = true, Text = "&Category:",
                                   Location = new Point(18, 104), TabIndex = 11))
let cbxCategories = new ComboBox(Location = new Point(85, 101),
                                 Width = 121, Sorted = true, TabIndex = 12)
autoPartNew.Controls.Add cbxCategories
let btnNewCategory = new Button(TabIndex = 13, Text = "New Ca&tegory...",
                                Location = new Point(212, 99), Width = 92)
autoPartNew.Controls.Add btnNewCategory;

let btnNewCategoryClick e =
    if category.ShowDialog() = DialogResult.OK then
        if String.IsNullOrEmpty(txtCategory.Text) = false then
            let strCategory = txtCategory.Text

            // Make sure the category is not yet in the list
            if cbxCategories.Items.Contains(strCategory) then
                MessageBox.Show(strCategory + " is already in the Categories combo box.",
                                "College Park Auto-Parts",
                                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
            else
                // Since this is a new category, add it to the combo box
                cbxCategories.Items.Add(strCategory) |> ignore

            cbxCategories.Text <- strCategory
        txtCategory.Text <- ""
btnNewCategory.Click.Add btnNewCategoryClick

// Part Name
autoPartNew.Controls.Add(new Label(AutoSize = true, Text = "&Part Name:",
                                   Location = new Point(18, 131), TabIndex = 14))
let txtPartName = new TextBox(Location = new Point(85, 128), Width = 255, TabIndex = 15)
autoPartNew.Controls.Add txtPartName

// Status
autoPartNew.Controls.Add(new Label(TabIndex = 16, Text = "&Status:",
                                   Location = new Point(18, 156), AutoSize = true))
let cbxPartsStatus = new ComboBox(Location = new Point(85, 153), Width = 121, TabIndex = 17);
cbxPartsStatus.Items.AddRange [| "New"; "Sold"; "Used"; "Damaged"; "Refurbished"; "Unknown" |]
autoPartNew.Controls.Add cbxPartsStatus

// Unit Price
autoPartNew.Controls.Add(new Label(AutoSize = true, Text = "&Unit Price:",
                                   Location = new Point(212, 157), TabIndex = 18))
let txtUnitPrice = new TextBox(Text = "0.00", TextAlign = HorizontalAlignment.Right,
                               Location = new Point(275, 154), Width = 65, TabIndex = 19)
autoPartNew.Controls.Add txtUnitPrice

// Part Number
autoPartNew.Controls.Add(new Label(Location = new Point(18, 183), TabIndex = 0,
                                   AutoSize = true, Text = "Part #:", Width = 65))

let txtPartNumber = new TextBox(Location = new Point(85, 180), Width = 65, TabIndex = 1)
autoPartNew.Controls.Add txtPartNumber

let autoPartNewLoad e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()

    for i = DateTime.Today.Year + 1 downto 1960 do
        cbxYears.Items.Add(sprintf "%i" i) |> ignore

    // Create a random number that will be used to identify the item
    let rnd : Random = new Random()
    txtPartNumber.Text <- sprintf "%i" (rnd.Next(100000, 999999))
    
    // This is the file that holds the list of parts
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs";

    if File.Exists(strAutoPartsFile) then
        use fsAutoParts = new FileStream(strAutoPartsFile,
                                         FileMode.Open, FileAccess.Read, FileShare.Read)
        // Retrieve the list of items from file
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>

        // Display the car manufacturers in the Make combo box
        for part in autoParts do
            if cbxMakes.Items.Contains(part.Make) = false then
                cbxMakes.Items.Add(part.Make) |> ignore

        // Display the pats categories in the Category combo box
        for part in autoParts do
            if cbxCategories.Items.Contains(part.Category) = false then
                cbxCategories.Items.Add(part.Category) |> ignore
autoPartNew.Load.Add autoPartNewLoad

// Button: Submit
let btnSubmit = new Button(Location = new Point(184, 190),
                           Width = 75, TabIndex = 20, Text = "&Submit")
autoPartNew.Controls.Add btnSubmit

let btnSubmitClick e =
    let bfAutoParts = new BinaryFormatter()

    Directory.CreateDirectory(@"C:\College Park Auto Parts") |> ignore

    // This is the file that holds the list of items
    let strAutoPartsFile : string =  @"C:\College Park Auto Parts\AutoParts.prs"

    if File.Exists(strAutoPartsFile) then
        use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        fsAutoParts.Close()

    let part = {
        PartNumber = int txtPartNumber.Text
        Year       = int cbxYears.Text
        Make       = cbxMakes.Text
        Model      = cbxModels.Text
        Category   = cbxCategories.Text
        PartName   = txtPartName.Text
        Status     = cbxPartsStatus.Text
        UnitPrice  = float txtUnitPrice.Text }

    autoParts.Add part

    use fsAutoParts = new FileStream(strAutoPartsFile,
                                     FileMode.Create, FileAccess.Write, FileShare.Write)
    bfAutoParts.Serialize(fsAutoParts, autoParts)
    fsAutoParts.Close()

    // After saving the item, reset the form
    cbxYears.Text       <- ""
    cbxMakes.Text       <- ""
    cbxModels.Text      <- ""
    cbxCategories.Text  <- ""
    txtPartName.Text    <- ""
    cbxPartsStatus.Text <- "New"
    txtUnitPrice.Text   <- "0.00"
    let rndNumber : Random = new Random()
    txtPartNumber.Text  <- sprintf "%i" (rndNumber.Next(100000, 999999))

btnSubmit.Click.Add btnSubmitClick

// Button: Close
let btnAutoPartNewClose = new Button(Width = 75, TabIndex = 21, Text = "Close",
                                     DialogResult = DialogResult.Cancel, Location = new Point(265, 190))
autoPartNew.Controls.Add btnAutoPartNewClose

let btnAutoPartNewCloseClick e = autoPartNew.Close()
btnAutoPartNewClose.Click.Add btnAutoPartNewCloseClick
// ---------------------------------------------------------------------------------
let autoPartEditor = new Form(MaximizeBox = false, MinimizeBox = false,
                           FormBorderStyle = FormBorderStyle.FixedDialog,
                           ClientSize = new System.Drawing.Size(360, 255),
                           StartPosition = FormStartPosition.CenterScreen,
                           Text = "College Park Auto-Parts: Auto Part Editor")

// Part Number
autoPartEditor.Controls.Add(new Label(AutoSize = true, Text = "Part #:",
                                   Location = new Point(17, 18), TabIndex = 0))

let txtEditorPartNumber = new TextBox(Location = new Point(85, 15), Width = 65, TabIndex = 1)
autoPartEditor.Controls.Add txtEditorPartNumber

// Button: Find
let btnFind = new Button(Location = new Point(160, 15), Width = 75, Text = "Find", TabIndex = 2)
autoPartEditor.Controls.Add btnFind

// Editor Year
autoPartEditor.Controls.Add(new Label(TabIndex = 3, Text = "&Year:",
                                      AutoSize = true, Location = new Point(17, 48)))
let cbxEditorYears = new ComboBox(Location = new Point(85, 45), Width = 65, TabIndex = 4)
autoPartEditor.Controls.Add cbxEditorYears

// Editor Make
autoPartEditor.Controls.Add(new Label(TabIndex = 5, Text = "M&ake:",
                                      Location = new Point(17, 78), AutoSize = true))
let cbxEditorMakes = new ComboBox(Location = new Point(85, 75), Width = 121, Sorted = true, TabIndex = 6)
autoPartEditor.Controls.Add cbxEditorMakes

// Button: Editor Make
let btnEditorNewMake = new Button(TabIndex = 7, Text = "New &Make...",
                            Location = new Point(212, 74), Width = 92)
autoPartEditor.Controls.Add btnEditorNewMake

// Editor Model
autoPartEditor.Controls.Add(new Label(AutoSize = true, Text = "M&odel:",
                                      Location = new Point(18, 104), TabIndex = 8))
let cbxEditorModels = new ComboBox(Location = new Point(85, 101),
                                   Sorted = true, TabIndex = 9, Width = 121)
autoPartEditor.Controls.Add cbxEditorModels

let btnEditorNewModel = new Button(TabIndex = 10, Text = "New Mo&del...",
                             Location = new Point(211, 99), Width = 92)
autoPartEditor.Controls.Add btnEditorNewModel

let cbxEditorMakesSelectedIndexChanged e =
    cbxEditorModels.Text <- ""
    cbxEditorModels.Items.Clear()

    for part in autoParts do
        if part.Make = cbxEditorMakes.Text then
            if cbxEditorModels.Items.Contains(part.Model) = false then
                cbxEditorModels.Items.Add(part.Model) |> ignore
cbxEditorMakes.SelectedIndexChanged.Add cbxEditorMakesSelectedIndexChanged

let btnEditorNewMakeClick e =
    let bfAutoParts : BinaryFormatter  = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if make.ShowDialog() = DialogResult.OK then
        
        if String.IsNullOrEmpty(txtMake.Text) = false then
            let strMake = txtMake.Text

            if File.Exists(strAutoPartsFile) then
                use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
                autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
                fsAutoParts.Close()
                        
                let makes : List<string> = new List<string>()

                autoParts.ForEach(fun (part : AutoPart) -> makes.Add(part.Make))

                makes.Sort()

                let bs : int = makes.BinarySearch(strMake)

                if bs >= 0 then
                    MessageBox.Show(strMake + " is already in the list at index " + bs.ToString() + ".",
                                    "College Park Auto-Parts",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
                else
                    cbxEditorMakes.Items.Add(strMake) |> ignore

                cbxEditorMakes.Text <- strMake
            txtMake.Text <- ""
btnEditorNewMake.Click.Add btnEditorNewMakeClick

let btnEditorNewModelClick e =
    let bfAutoParts : BinaryFormatter  = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if model.ShowDialog() = DialogResult.OK then
        if String.IsNullOrEmpty(txtModel.Text) = false then
            let strModel = txtModel.Text

            if File.Exists(strAutoPartsFile) then
                use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
                autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
                fsAutoParts.Close()
                        
                let models : List<string> = new List<string>()

                autoParts.ForEach(fun (part : AutoPart) -> models.Add(part.Model))

                models.Sort()

                if models.Contains(strModel) then
                    MessageBox.Show(strModel + " is already in the Models combo box.",
                                    "College Park Auto-Parts",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
            else
                // Since this is a new model, add it to the combo box
                cbxEditorModels.Items.Add(strModel) |> ignore

            cbxEditorModels.Text <- strModel
        txtModel.Text <- ""
btnEditorNewModel.Click.Add btnEditorNewModelClick

// Editor Category
autoPartEditor.Controls.Add(new Label(AutoSize = true, Text = "&Category:",
                                   Location = new Point(18, 131), TabIndex = 11))
let cbxEditorCategories = new ComboBox(Location = new Point(85, 128),
                                 Width = 121, Sorted = true, TabIndex = 12)
autoPartEditor.Controls.Add cbxEditorCategories
let btnEditorNewCategory = new Button(TabIndex = 13, Text = "New Ca&tegory...",
                                      Location = new Point(212, 126), Width = 92)
autoPartEditor.Controls.Add btnEditorNewCategory;

let btnEditorNewCategoryClick e =
    let bfAutoParts : BinaryFormatter  = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if category.ShowDialog() = DialogResult.OK then
        if String.IsNullOrEmpty(txtCategory.Text) = false then
            let strCategory = txtCategory.Text

            if File.Exists(strAutoPartsFile) then
                use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
                autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
                fsAutoParts.Close()

                let categories : List<string> = new List<string>()

                autoParts.ForEach(fun (part : AutoPart) -> categories.Add(part.Category))

                let categoryExists = categories.Exists(fun category -> category = strCategory)

                if categoryExists = true then
                    MessageBox.Show(strCategory + " is already in the list.",
                                    "College Park Auto-Parts",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
                else
                    cbxEditorCategories.Items.Add(strCategory) |> ignore

            cbxEditorCategories.Text <- strCategory
        txtCategory.Text <- ""
btnEditorNewCategory.Click.Add btnEditorNewCategoryClick

// Part Name
autoPartEditor.Controls.Add(new Label(AutoSize = true, Text = "&Part Name:",
                                   Location = new Point(18, 158), TabIndex = 14))
let txtEditorPartName = new TextBox(Location = new Point(85, 155), Width = 255, TabIndex = 15)
autoPartEditor.Controls.Add txtEditorPartName

// Status
autoPartEditor.Controls.Add(new Label(TabIndex = 16, Text = "&Status:",
                                      Location = new Point(18, 183), AutoSize = true))
let cbxEditorPartsStatus = new ComboBox(Location = new Point(85, 180), Width = 121, TabIndex = 17);
cbxEditorPartsStatus.Items.AddRange [| "New"; "Sold"; "Used"; "Damaged"; "Refurbished"; "Unknown" |]
autoPartEditor.Controls.Add cbxEditorPartsStatus

// Unit Price
autoPartEditor.Controls.Add(new Label(AutoSize = true, Text = "&Unit Price:",
                                      Location = new Point(212, 184), TabIndex = 18))
let txtEditorUnitPrice = new TextBox(Text = "0.00", TextAlign = HorizontalAlignment.Right,
                               Location = new Point(275, 181), Width = 65, TabIndex = 19)
autoPartEditor.Controls.Add txtEditorUnitPrice

let autoPartEditorLoad e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()

    for i = DateTime.Today.Year + 1 downto 1960 do
        cbxEditorYears.Items.Add(sprintf "%i" i) |> ignore
    
    // This is the file that holds the list of parts
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs";

    if File.Exists(strAutoPartsFile) then
        use fsAutoParts = new FileStream(strAutoPartsFile,
                                         FileMode.Open, FileAccess.Read, FileShare.Read)
        // Retrieve the list of items from file
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>

        // Display the car manufacturers in the Make combo box
        for part in autoParts do
            if cbxEditorMakes.Items.Contains(part.Make) = false then
                cbxEditorMakes.Items.Add(part.Make) |> ignore

        // Display the pats categories in the Category combo box
        for part in autoParts do
            if cbxEditorCategories.Items.Contains(part.Category) = false then
                cbxEditorCategories.Items.Add(part.Category) |> ignore
autoPartEditor.Load.Add autoPartEditorLoad

let btnFindClick e =
    let bfAutoParts = new BinaryFormatter()
    let strAutoPartsFile : string =  @"C:\College Park Auto Parts\AutoParts.prs"

    if File.Exists(strAutoPartsFile) then
        use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        fsAutoParts.Close()

    let autoPartExists = autoParts.Exists(fun part -> part.PartNumber = int txtEditorPartNumber.Text)

    if autoPartExists = true then
        let part : AutoPart = autoParts.Find(fun item -> item.PartNumber = int txtEditorPartNumber.Text)
        cbxEditorYears.Text <- sprintf "%d" part.Year
        cbxEditorMakes.Text <- part.Make
        cbxEditorModels.Text <- part.Model
        cbxEditorCategories.Text <- part.Category
        txtEditorPartName.Text <- part.PartName
        cbxEditorPartsStatus.Text <- part.Status
        txtEditorUnitPrice.Text <- sprintf "%0.02f" part.UnitPrice 

btnFind.Click.Add btnFindClick

// Button: Submit
let btnEditorSubmit = new Button(Location = new Point(184, 215),
                           Width = 75, TabIndex = 20, Text = "&Submit")
autoPartEditor.Controls.Add btnEditorSubmit

let btnEditorSubmitClick e =
    let bfAutoParts = new BinaryFormatter()
    let strAutoPartsFile : string =  @"C:\College Park Auto Parts\AutoParts.prs"

    if File.Exists(strAutoPartsFile) then
        use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        
    let autoPartExists = autoParts.Exists(fun part -> part.PartNumber = int txtEditorPartNumber.Text)
    if autoPartExists = true then
        let part : AutoPart = autoParts.Find(fun item -> item.PartNumber = int txtEditorPartNumber.Text)

        part.Year <- int cbxEditorYears.Text
        part.Make <- cbxEditorMakes.Text;
        part.Model <- cbxEditorModels.Text;
        part.Category <- cbxEditorCategories.Text;
        part.PartName <- txtEditorPartName.Text;
        part.Status <- cbxEditorPartsStatus.Text;
        part.UnitPrice <- float txtEditorUnitPrice.Text

        use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Create, FileAccess.Write, FileShare.Write)
        bfAutoParts.Serialize(fsAutoParts, autoParts)

        autoPartEditor.Close()
btnEditorSubmit.Click.Add btnEditorSubmitClick

// Button: Close
let btnEditorClose = new Button(Width = 75, TabIndex = 21, Text = "Close",
                          DialogResult = DialogResult.Cancel, Location = new Point(265, 215))
autoPartEditor.Controls.Add btnEditorClose

let btnEditorCloseClick e = autoPartEditor.Close()
btnEditorClose.Click.Add btnEditorCloseClick
// ---------------------------------------------------------------------------------
let collegeParkAutoParts = new Form(ClientSize = new System.Drawing.Size(746, 596),
                                    StartPosition = FormStartPosition.CenterScreen,
                                    MaximizeBox = false, Text = "College Park Auto-Parts")
// List View Columns
let colPartNumber = new ColumnHeader(Text = "Part #")
let colYear = new ColumnHeader(Text = "Year", TextAlign = HorizontalAlignment.Center, Width = 40)
let colMake = new ColumnHeader(Text = "Make", Width = 65)
let colModel = new ColumnHeader(Text = "Model", Width = 70)
let colCategory = new ColumnHeader(Text = "Category", Width = 80)
let colPartName = new ColumnHeader(Text = "Part Name", Width = 250)
let colStatus = new ColumnHeader(Text = "Status")
let colUnitPrice = new ColumnHeader(Text = "Unit Price", TextAlign = HorizontalAlignment.Right)

// List View: Auto-Parts
let lvwAutoParts = new ListView(FullRowSelect = true, GridLines = true,
                                View = System.Windows.Forms.View.Details,
                                Location = new Point(14, 17), Size = new System.Drawing.Size(712, 362))
lvwAutoParts.Anchor <- AnchorStyles.Top ||| AnchorStyles.Bottom ||| AnchorStyles.Left ||| AnchorStyles.Right
lvwAutoParts.Columns.AddRange [| colPartNumber; colYear; colMake; colModel; colCategory; colPartName; colStatus; colUnitPrice|]
collegeParkAutoParts.Controls.Add lvwAutoParts

// Capacity
collegeParkAutoParts.Controls.Add(new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left),
                                            AutoSize = true, Location = new Point(16, 398), Text = "Capacity:"))                             
let txtCapacity = new TextBox(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Location = new Point(73, 396), Width = 63)
collegeParkAutoParts.Controls.Add txtCapacity

// Count
collegeParkAutoParts.Controls.Add(new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left),
                                            AutoSize = true, Location = new Point(16, 427), Text = "Inventory:"))
let txtCount = new TextBox(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Location = new Point(73, 424), Width = 63)
collegeParkAutoParts.Controls.Add txtCount
collegeParkAutoParts.Controls.Add(new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left),
                                            AutoSize = true, Location = new Point(142, 427), Text = "Auto Parts"))

// Selected Index
collegeParkAutoParts.Controls.Add(new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left),
                                            AutoSize = true, Location = new Point(250, 398), Text = "Index of Selected Item:"))                             
let txtSelectedIndex = new TextBox(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Location = new Point(372, 396), Width = 63)
collegeParkAutoParts.Controls.Add txtSelectedIndex

// Finding parts of a manufacturer
collegeParkAutoParts.Controls.Add(new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left),
                                            AutoSize = true, Location = new Point(250, 427), Text = "Find Parts For:"))                             
let txtFindPartsFor = new TextBox(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Location = new Point(372, 424), Width = 63)
collegeParkAutoParts.Controls.Add txtFindPartsFor
let btnFindPartsFor = new Button(Location = new Point(445, 424), Width = 62,
                                Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Text = "Find")
collegeParkAutoParts.Controls.Add btnFindPartsFor

// Button: New Auto-Part
let btnNewAutoPart = new Button(Location = new Point(16, 460), Size = new System.Drawing.Size(210, 34),
                                Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Text = "New Au&to Part...")
collegeParkAutoParts.Controls.Add btnNewAutoPart

// Button: Add Range
let btnAddRange = new Button(Location = new Point(258, 460), Text = "Add Range",
                             Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Size = new System.Drawing.Size(210, 34))
collegeParkAutoParts.Controls.Add btnAddRange

// Button: Remove an Item Selected from the List View
let btnRemoveAutoPart = new Button(Location = new Point(498, 460), Text = "Remove Selected Auto-Part",
                                   Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Size = new System.Drawing.Size(210, 34))
collegeParkAutoParts.Controls.Add btnRemoveAutoPart

// Button: Insert Range
let btnInsertRange = new Button(Size = new System.Drawing.Size(210, 34), Location = new Point(16, 502),
                                Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Text = "Insert Range")
collegeParkAutoParts.Controls.Add btnInsertRange

// Button: Update Auto-Part
let btnUpdateAutoPart = new Button(Location = new Point(258, 502), Size = new System.Drawing.Size(210, 34),
                                   Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Text = "&Update Auto Part...")
collegeParkAutoParts.Controls.Add btnUpdateAutoPart

// Button: Remove Items Based on a criterion
let btnRemoveDamagedParts = new Button(Location = new Point(498, 502), Text = "Remove Damaged Auto-Parts",
                                       Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Size = new System.Drawing.Size(210, 34))
collegeParkAutoParts.Controls.Add btnRemoveDamagedParts

// Button: Clear the Inventory
let btnClearInventory = new Button(Location = new Point(16, 544), Text = "Clear Inventory",
                                   Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Size = new System.Drawing.Size(210, 34))
collegeParkAutoParts.Controls.Add btnClearInventory

// Button: Show all Auto-Parts
let btnShowAutoParts = new Button(Location = new Point(258, 544), Text = "Show all Auto-Parts",
                                   Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Size = new System.Drawing.Size(210, 34))
collegeParkAutoParts.Controls.Add btnShowAutoParts

let showAutoParts() =
    let bfAutoParts : BinaryFormatter  = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if File.Exists(strAutoPartsFile) then
        lvwAutoParts.Items.Clear();

        use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)

        // Retrieve the list of parts from file
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        fsAutoParts.Close()

        autoParts.ForEach(fun (part : AutoPart) ->
            let lviAutoPart : ListViewItem  = new ListViewItem(sprintf "%i" part.PartNumber)

            lviAutoPart.SubItems.Add(sprintf "%i" part.Year) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Make) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Model) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Category) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.PartName) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Status) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%0.02f" part.UnitPrice) |> ignore
            lvwAutoParts.Items.Add lviAutoPart |> ignore )
              
    txtCapacity.Text <- sprintf "%i" autoParts.Capacity
    txtCount.Text    <- sprintf "%i" autoParts.Count

let collegeParkAutoPartsLoad e =
    showAutoParts()
collegeParkAutoParts.Load.Add collegeParkAutoPartsLoad

let btnNewAutoPartClick e =
    autoPartNew.ShowDialog() |> ignore
    showAutoParts()
btnNewAutoPart.Click.Add btnNewAutoPartClick

let btnUpdateAutoPartClick e =
    autoPartEditor.ShowDialog() |> ignore
    showAutoParts()
btnUpdateAutoPart.Click.Add btnUpdateAutoPartClick

let btnShowAutoPartsClick e =
    showAutoParts()
btnShowAutoParts.Click.Add btnShowAutoPartsClick

// Button: Add Range
let btnAddRangeClick e =
    let bfmAutoParts : BinaryFormatter = new BinaryFormatter()
    // Create a random number that will be used to identify the item
    let rndNumber : Random  = new Random();
    let strFileName  : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if File.Exists(strFileName) then
        use fsAutoParts = new FileStream(strFileName, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfmAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        fsAutoParts.Close()

    autoParts.AddRange [| { PartNumber = rndNumber.Next(100000, 999999); Year = 2012; Make = "Chevrolet"; Model = "Malibu 2.4L L4"; Category = "Brake System"; PartName = "Brake Pads"; Status = "New"; UnitPrice = 24.25 }
                          { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Toyota";    Model = "Corolla 1.8L L4"; Category = "Exhaust & Emissions"; PartName = "Shepherd Auto Parts 2-1/4\" Weld On Stainless Steel 1 Slanted Round Exhaust Muffler"; Status = "New"; UnitPrice = 25.95 }
                          { PartNumber = rndNumber.Next(100000, 999999); Year = 2014; Make = "Dodge";     Model = "Charger 3.6L V6 Gas AWD"; Category = "Brake System"; PartName = "StopTech 309.10530 Street Performance Rear Brake Pad"; Status = "Used"; UnitPrice = 62.50 }
                          { PartNumber = rndNumber.Next(100000, 999999); Year = 2010; Make = "Chevrolet"; Model = "Malibu 2.4L L4 Gas"; Category = "Air Filters"; PartName = "Fram CA9492 Extra Guard Flexible Panel Air Filter"; Status = "Damaged"; UnitPrice = 9.25 }
                          { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Toyota";    Model = "Corolla 1.8L L4"; Category = "Exhaust & Emissions"; PartName = "Shepherd Auto Parts 2-1/4\" Weld On Stainless Steel 2 Slanted Round Exhaust Muffler"; Status = "Damaged"; UnitPrice = 45.55 }
                          { PartNumber = rndNumber.Next(100000, 999999); Year = 2014; Make = "Dodge";     Model = "Charger 3.6L V6 Gas AWD"; Category = "Brake System"; PartName = "Power Stop K1719 Rear Ceramic Brake Pad and Cross Drilled/Slotted Combo Rotor One-Click Brake Kit"; Status = "New"; UnitPrice = 142.95 } |]

    use fsAutoParts = new FileStream(strFileName, FileMode.Create, FileAccess.Write, FileShare.Write)
    bfmAutoParts.Serialize(fsAutoParts, autoParts)
    fsAutoParts.Close()

    MessageBox.Show("A range of auto parts has been added to the inventory.",
                    "College Park Auto-Parts",
                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
btnAddRange.Click.Add btnAddRangeClick
collegeParkAutoParts.Controls.Add btnAddRange

// Button: Insert Range
let btnInsertRangeClick e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strFileName : string = @"C:\College Park Auto Parts\AutoParts.prs"

    let rndNumber : Random = new Random()

    if File.Exists(strFileName) then
        use fsAutoParts = new FileStream(strFileName, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        fsAutoParts.Close()

        autoParts.InsertRange(5, [| { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Toyota"; Model = "Camry 2.5L L4"; Category = "Steering System"; PartName = "Beck Arnley 103-3068 Steering Rack Boot Kit"; Status = "Used"; UnitPrice = 9.55 }
                                    { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Nissan"; Model = "Sentra SE-R 2.5L L4"; Category = "Struts & Suspension"; PartName = "Monroe 72378 OESpectrum Sensa-Trac Front Strut Assembly"; Status = "New"; UnitPrice = 59.95 }
                                    { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Toyota"; Model = "Camry 2.5L L4"; Category = "Struts & Suspension"; PartName = "NGK 4469 Spark Plug"; Status = "Damaged"; UnitPrice = 59.95 }
                                    { PartNumber = rndNumber.Next(100000, 999999); Year = 2008; Make = "Toyota"; Model = "Camry 2.5L L4"; Category = "Air Filters"; PartName = "FRAM CF10285 Fresh Breeze Cabin Air Filter"; Status = "New"; UnitPrice = 12.25 } |])

        use fsAutoParts = new FileStream(strFileName, FileMode.Create, FileAccess.Write, FileShare.Write)
        bfAutoParts.Serialize(fsAutoParts, autoParts)
        fsAutoParts.Close()
        
        MessageBox.Show("A range of auto parts has been inserted into the inventory.",
                        "College Park Auto-Parts",
                        MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
        
btnInsertRange.Click.Add btnInsertRangeClick
collegeParkAutoParts.Controls.Add btnInsertRange

let lvwAutoPartsSelectedIndexChanged e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if lvwAutoParts.SelectedItems.Count > 0 then
        let lSelectedNumber = int lvwAutoParts.SelectedItems.[0].Text
 
        if File.Exists(strAutoPartsFile) then
            use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
            autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
            fsAutoParts.Close()

            if File.Exists(strAutoPartsFile) then
                use fsAutoParts : FileStream = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
                autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
                fsAutoParts.Close()

                txtSelectedIndex.Text <- sprintf "%i" (autoParts.FindIndex(fun part -> part.PartNumber = lSelectedNumber))
lvwAutoParts.SelectedIndexChanged.Add lvwAutoPartsSelectedIndexChanged

// Finding parts of a manufacturer
let btnFindPartsForClick e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    if File.Exists(strAutoPartsFile) then
        lvwAutoParts.Items.Clear()

        use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        fsAutoParts.Close()

        let makeParts : List<AutoPart> = autoParts.FindAll(fun part -> part.Make.Contains(txtFindPartsFor.Text))

        if String.IsNullOrEmpty(txtFindPartsFor.Text) = false then
            makeParts.ForEach(fun part ->
            let lviAutoPart : ListViewItem  = new ListViewItem(sprintf "%i" part.PartNumber)

            lviAutoPart.SubItems.Add(sprintf "%i" part.Year) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Make) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Model) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Category) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.PartName) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%s" part.Status) |> ignore
            lviAutoPart.SubItems.Add(sprintf "%0.02f" part.UnitPrice) |> ignore
            lvwAutoParts.Items.Add lviAutoPart |> ignore )
              
    txtCapacity.Text <- sprintf "%i" autoParts.Capacity
    txtCount.Text    <- sprintf "%i" autoParts.Count

btnFindPartsFor.Click.Add btnFindPartsForClick

let btnRemoveAutoPartClick e =
    let mutable lPartNumber = 0
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strAutoPartsFile  : string = @"C:\College Park Auto Parts\AutoParts.prs"
    
    if lvwAutoParts.SelectedItems.Count > 0 then
        lPartNumber <- int (lvwAutoParts.SelectedItems.[0]).Text
        
        if MessageBox.Show("Are you sure you want to delete the selected auto-part?",
                           "College Park Auto-Parts",
                           MessageBoxButtons.YesNo, MessageBoxIcon.Information) = DialogResult.Yes then
        
            if File.Exists(strAutoPartsFile) then
                let fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
                autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
                fsAutoParts.Close()

                let selected : AutoPart = autoParts.Find(fun part -> part.PartNumber = int (lvwAutoParts.SelectedItems.[0]).Text)

                let removed = autoParts.Remove(selected)

                if removed = true then
                    MessageBox.Show("The selected item has been removed from the inventory.",
                                    "College Park Auto-Parts",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
                    // Save the new list
                    use fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Create, FileAccess.Write, FileShare.Write)
                    bfAutoParts.Serialize(fsAutoParts, autoParts)
                else
                    MessageBox.Show("For some reason, the item was not removed from the inventory.",
                                    "College Park Auto-Parts",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
    else
        MessageBox.Show("You must first select the item to be removed from the inventory.",
                        "College Park Auto-Parts",
                         MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
    showAutoParts()
btnRemoveAutoPart.Click.Add btnRemoveAutoPartClick

let btnRemoveDamagedPartsClick e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strAutoPartsFile  : string = @"C:\College Park Auto Parts\AutoParts.prs"
        
    if File.Exists(strAutoPartsFile) then
        let fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
        autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
        fsAutoParts.Close()
    
        if MessageBox.Show("Are you sure you want to delete all damaged auto-parts?",
                           "College Park Auto-Parts",
                           MessageBoxButtons.YesNo, MessageBoxIcon.Information) = DialogResult.Yes then

            let damagedPartsRemoved = autoParts.RemoveAll(fun part -> part.Status = "Damaged")

            if damagedPartsRemoved > 0 then
                let fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Create, FileAccess.Write, FileShare.Write)
                bfAutoParts.Serialize(fsAutoParts, autoParts)
                fsAutoParts.Close()

                MessageBox.Show("All damaged items have been removed from the inventory.",
                                "College Park Auto-Parts",
                                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
                showAutoParts()
            else
                MessageBox.Show("For some reason, the item was not removed from the inventory.",
                                "College Park Auto-Parts",
                                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
btnRemoveDamagedParts.Click.Add btnRemoveDamagedPartsClick

let btnClearInventoryClick e =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strAutoPartsFile  : string = @"C:\College Park Auto Parts\AutoParts.prs"
        
    if File.Exists(strAutoPartsFile) then
        if MessageBox.Show("Are you sure you want to clear the inventory?",
                           "College Park Auto-Parts",
                           MessageBoxButtons.YesNo, MessageBoxIcon.Information) = DialogResult.Yes then
            let fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
            autoParts <- bfAutoParts.Deserialize(fsAutoParts) :?> List<AutoPart>
            fsAutoParts.Close()

            autoParts.Clear()
            
            let fsAutoParts = new FileStream(strAutoPartsFile, FileMode.Create, FileAccess.Write, FileShare.Write)
            bfAutoParts.Serialize(fsAutoParts, autoParts)
            fsAutoParts.Close()

            showAutoParts()
btnClearInventory.Click.Add btnClearInventoryClick

let createDefaultObjects() =
    let bfAutoParts : BinaryFormatter = new BinaryFormatter()
    let strAutoPartsFile : string = @"C:\College Park Auto Parts\AutoParts.prs"

    autoParts.Add { PartNumber = 856591; Year = 2015; Make = "Ford";      Model = "Escape SE 2.0L L4 FWD"; Category = "Oil Filters"; PartName = "K&amp;N PS-1002 Pro Series Oil Filter"; Status = "New"; UnitPrice = 7.75 }
    autoParts.Add { PartNumber = 544996; Year = 2012; Make = "Toyota";    Model = "Corolla LE 1.8L L4";    Category = "Spark Plugs"; PartName = "NGK (4912) ILKAR7B11 (4912) Laser Iridium Spark Plug, Pack of 1"; Status = "New"; UnitPrice = 11.95 }
    autoParts.Add { PartNumber = 868727; Year = 2008; Make = "Chevrolet"; Model = "Aveo LS 1.6L L4";       Category = "Cables";      PartName = "Auto 7 (928-0087) Hood Latch Release Cable"; Status = "New"; UnitPrice = 26.95 }
    autoParts.Add { PartNumber = 716452; Year = 2012; Make = "Toyota";    Model = "Corolla LE 1.8L L4";    Category = "Fuel System"; PartName = "CST 5791 Locking Fuel Cap"; Status = "New"; UnitPrice = 12.75 }
    autoParts.Add { PartNumber = 898512; Year = 2010; Make = "Dodge";     Model = "Charger SE 3.5L V6";    Category = "Spark Plugs"; PartName = "Bosch 09005 Premium Spark Plug Wire Set"; Status = "Damaged"; UnitPrice = 31.25 }
    autoParts.Add { PartNumber = 918336; Year = 2013; Make = "Buick";     Model = "Regal";                 Category = "Steering System"; PartName = "Monroe SA1997 Shock-Mate Shock Absorber Boot Kit"; Status = "New"; UnitPrice = 7.25 }
    autoParts.Add { PartNumber = 338143; Year = 2012; Make = "Honda";     Model = "Accord EX";             Category = "Batteries"; PartName = "AutoCraft Gold Battery, Group Size 24, 700 CCA"; Status = "New"; UnitPrice = 138.35 }
    autoParts.Add { PartNumber = 742467; Year = 2008; Make = "Toyota";    Model = "Corolla LE 1.8L L4";    Category = "Oil Filters"; PartName = "K&amp;N PS-1003 Pro Series Oil Filter"; Status = "Used"; UnitPrice = 7.5 }
    autoParts.Add { PartNumber = 565512; Year = 2015; Make = "Ford";      Model = "Escape SE 2.0L L4 FWD"; Category = "Brake System"; PartName = "ACDelco 14D1095CH Advantage Ceramic Rear Disc Brake Pad Set with Hardware"; Status = "Refurbished"; UnitPrice = 22.25 }
    autoParts.Add { PartNumber = 645923; Year = 2008; Make = "Chevrolet"; Model = "Aveo LS 1.6L L4";       Category = "Cables"; PartName = "APDTY 0391198 Hood Latch Release Cable"; Status = "New"; UnitPrice = 42.35 }
    autoParts.Add { PartNumber = 624324; Year = 2012; Make = "Toyota";    Model = "Corolla LE 1.8L L4";    Category = "Fuel System"; PartName = "Motorad MGC-791 Locking Fuel Cap"; Status = "Damaged"; UnitPrice = 3.00 }
    autoParts.Add { PartNumber = 726270; Year = 2008; Make = "Toyota";    Model = "Corolla LE 1.8L L4";    Category = "Oil Filters"; PartName = "Mobil 1 M1-103 Extended Performance Oil Filter"; Status = "New"; UnitPrice = 12.25 }
    autoParts.Add { PartNumber = 817569; Year = 2010; Make = "Dodge";     Model = "Charger SE 3.5L V6";    Category = "Brake System"; PartName = "Callahan FRONT 320.04 mm + Solid REAR 320 mm Premium OE [4] Rotors + [8] Quiet Low Dust Ceramic Brake Pads Kit"; Status = "New"; UnitPrice = 195.00 }
    autoParts.Add { PartNumber = 796018; Year = 2008; Make = "Chevrolet"; Model = "Aveo LS 1.6L L4";       Category = "Steering System"; PartName = "Cardone 20-0038F Remanufactured Domestic Power Steering Filter"; Status = "New"; UnitPrice = 16.25 }
    autoParts.Add { PartNumber = 171405; Year = 2012; Make = "Toyota";    Model = "Corolla LE 1.8L L4";    Category = "Spark Plugs"; PartName = "ACDelco 41-110 Professional Iridium Spark Plug (Pack of 1)"; Status = "Damaged"; UnitPrice = 5.65 }
    autoParts.Add { PartNumber = 483074; Year = 2010; Make = "Dodge";     Model = "Charger SE 3.5L V6";    Category = "Brake System"; PartName = "Dura International BP1057 C Rear Ceramic Brake Pad"; Status = "New"; UnitPrice = 12.95 }

    use fsAutoParts = new FileStream(strAutoPartsFile,
                                     FileMode.Create, FileAccess.Write, FileShare.Write)
    bfAutoParts.Serialize(fsAutoParts, autoParts)
    fsAutoParts.Close()

// Button: Close the Application
let btnApplicationClose = new Button(Location = new Point(498, 544), Text = "Close Application",
                                     Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), Size = new System.Drawing.Size(210, 34))

let btnApplicationCloseClick _ =
    // createDefaultObjects()
    collegeParkAutoParts.Close()
btnApplicationClose.Click.Add btnApplicationCloseClick
collegeParkAutoParts.Controls.Add btnApplicationClose

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

This would produce:

College Park Auto Parts

College Park Auto Parts

College Park Auto Parts

College Park Auto Parts

   
   
 

Home Copyright © 2015, FunctionX Home