Using LINQ to Objects in Visual Basic

This article provides an introduction to employing LINQ to Objects queries to support a simple Visual Basic Win Forms application.

Introduction

This article provides an introduction to employing LINQ to Objects queries to support a simple win forms application; the article addresses the construction of LINQ to Objects statements and then goes on to describe how one might use LINQ to Objects within the context of an actual application.

The demonstration project included with the article is a simple contact manager which may be used to capture and store information about a person's contacts in address book format.  This demonstration application uses LINQ to Objects to manage, query, and order the list of contacts maintained by the application.  The demonstration application also includes a dummy contact file with a collection of test contacts.


 
Figure 1:  Application Main Form

The application provides the following functionality:

  • Create a contact data file.
  • Add contacts to the contact data file.
  • Remove contacts from the contact data file.
  • Search for specific contacts by last name.
  • Create and edit details about the contact.
    • First Name
    • Middle Name
    • Last Name
    • Street
    • City
    • State
    • Zip Code
    • Home Phone
    • Work Phone
    • Cell Phone
    • Email Address
  • Save a contact data file.
  • Reopen a contact data file.
  • Navigate through all of the contacts in the contact data file.
  • View a list of all contacts in the contact data file.
  • Provide a Rolodex function (search by starting letter of last name)

Naturally, the approaches used within the application are representative of only one way of doing things; as with most things in the .NET world, there are several alternatives and you can modify the code to work with the data using one of the other alternatives if you prefer to do so.

Figure 2:  Searching for a Contact by Last Name

Figure 3:  Listing All Contacts (Edits to the grid are posted immediately to the List)

Figure 4:  Rolodex Function

LINQ to Objects Statements

This section will discuss some of the common techniques used in LINQ to Objects statement construction.  In a nutshell, LINQ to Objects provides the developer with the means to conduct queries against an in-memory collection of objects.  The techniques used to query against such collections of objects are similar to but simpler than the approaches used to conduct queries against a relational database using SQL statements.

Anatomy of LINQ to Objects Statements

Example 1 � A Simple Select

This is an example of a very simple LINQ to Objects statement:

Public Sub Example1()

 

        Dim tools() As String = {"Tablesaw", "Bandsaw", "Planer", "Jointer", "Drill",

                                 "Sander"}

 

        Dim List = From t In tools _

                       Select t

 

        Dim sb As New StringBuilder()

        Dim s As String

 

        For Each s In List

            sb.Append(s + Environment.NewLine)

        Next

 

        MessageBox.Show(sb.ToString(), "Tools")

 

End Sub

In the example, an array of strings (tools) is used as the collections objects to be queries using LINQ to Objects; the LINQ to Objects query is:

        Dim List = From t In tools _

                       Select t

In this example, an untyped variable "List" is created and all of the items contained in the string array are added to this object; the types are inferred (implicitly typed), for example, "t" is a member of tools, since it is known that tools is a string array, the framework will infer that "t" is also a string.  Of course this is not all that terrific since you can just iterate through the array to do essentially the same thing; however, you can create more complex queries with LINQ to Objects and then the value of the LINQ library becomes more apparent.

If you were to create a project, add this bit of code to a method and run it, the results would look like this:

Figure 5:  Query Results

Example 2 � Select with a Where Clause

The next example shows a LINQ to Objects query that incorporates a where clause.  In this example, we start out with a collection of birds in the form of a string array; LINQ to Objects is used to query this string array to find and return a subset of the array in the form of all birds with names beginning with the letter "R".

Public Sub Example2()

 

        Dim Birds() As String = {"Indigo Bunting", "Rose Breasted Grosbeak", _

                                 "Robin", "House Finch", "Gold Finch", _

                                 "Ruby Throated Hummingbird", _

                                 "Rufous Hummingbird", "Downy Woodpecker"}

 

        Dim list = From b In Birds _

                   Where b.StartsWith("R") _

                   Select b

 

        Dim sb As New StringBuilder()

        Dim s As String

 

        For Each s In list

            sb.Append(s + Environment.NewLine)

        Next

 

        MessageBox.Show(sb.ToString(), "R Birds")

 

End Sub

If you were to run this query, the results would appear as follows (all birds with names beginning with the letter "R" are shown):

Figure 6:  R Birds Query Results

Example 3 � Select with a Where Clause

In a slight variation to the previous query, this example looks for an exact match in its where clause:

    Public Sub Example3()

 

        Dim Birds() As String = {"Indigo Bunting", "Rose Breasted Grosbeak", _

                                 "Robin", "House Finch", "Gold Finch", _

                                 "Ruby Throated Hummingbird", _

                                 "Rufous Hummingbird", "Downy Woodpecker"}

 

        Dim list = From b In Birds _

                   Where b = "Indigo Bunting" _

                   Select b

 

        Dim sb As New StringBuilder()

        Dim s As String

 

        For Each s In list

            sb.Append(s + Environment.NewLine)

        Next

 

        MessageBox.Show(sb.ToString(), "Bunting Birds"

    End Sub

Running this code will result in the display of this message box:

Figure 7:  Bird Query Results

Example 4 � Generating an Ordered List

In this query, the list of birds is alphabetized (using "Order By b Ascending"):

    Public Sub Example4()

 

        Dim Birds() As String = {"Indigo Bunting", "Rose Breasted Grosbeak", _

                                 "Robin", "House Finch", "Gold Finch", _

                                 "Ruby Throated Hummingbird", _

                                 "Rufous Hummingbird", "Downy Woodpecker"}

 

        Dim list = From b In Birds _

                   Order By b Ascending _

                   Select b

 

        Dim sb As New StringBuilder()

        Dim s As String

 

        For Each s In list

            sb.Append(s + Environment.NewLine)

        Next

 

        MessageBox.Show(sb.ToString(), "Ordered Birds"

    End Sub

Figure 8:  Ordered Bird List Query Results

Example 5 � Working with a Custom Type

In this example, a typed list is created, populated, and then queried using LINQ to Objects.

    Public Sub Example5()

 

        Dim parts = New List(Of Parts)

 

        Dim p1 As New Parts()

        p1.PartNumber = 1

        p1.PartDescription = "Cog"

        parts.Add(p1)

 

        Dim p2 As New Parts()

        p2.PartNumber = 2

        p2.PartDescription = "Widget"

        parts.Add(p2)

 

        Dim p3 As New Parts()

        p3.PartNumber = 3

        p3.PartDescription = "Gear"

        parts.Add(p3)

 

        Dim p4 As New Parts()

        p4.PartNumber = 4

        p4.PartDescription = "Tank"

        parts.Add(p4)

 

        Dim p5 = New Parts()

        p5.PartNumber = 5

        p5.PartDescription = "Piston"

        parts.Add(p5)

 

        Dim p6 As New Parts()

        p6.PartNumber = 6

        p6.PartDescription = "Shaft"

        parts.Add(p6)

 

        Dim p7 As New Parts()

        p7.PartNumber = 7

        p7.PartDescription = "Pulley"

        parts.Add(p7)

 

        Dim p8 As New Parts()

        p8.PartNumber = 8

        p8.PartDescription = "Sprocket"

        parts.Add(p8)

 

 

        Dim list = From p In parts _

                       Order By p.PartNumber Ascending _

                       Select p

 

        Dim sb As New StringBuilder()

        Dim pt As Parts

 

        For Each pt In parts

sb.Append(pt.PartNumber.ToString() + ": " + _ pt.PartDescription.ToString() + _

            Environment.NewLine)

        Next

 

        MessageBox.Show(sb.ToString(), "Parts List"

    End Sub

The purpose the query is merely to sort the parts list in order of the part numbers.  The results returned from this method are as follows:

Figure 9:  Ordered Parts List Query

The parts class used in as the type behind the parts list is as follows:

Public Class Parts

 

    Private mPartNumber As Integer

    Private mPartDescription As String 

 

    Public Sub New()

        ' nothing

    End Sub 

 

    Public Sub New(ByVal partNum As Integer, ByVal partDesc As String)

        mPartNumber = partNum

        mPartDescription = partDesc

    End Sub 

 

    Public Property PartNumber() As Integer

        Get

            Return mPartNumber

        End Get

        Set(ByVal value As Integer)

            mPartNumber = value

        End Set

    End Property 

 

    Public Property PartDescription() As String

        Get

            Return mPartDescription

        End Get

        Set(ByVal value As String)

            mPartDescription = value

        End Set

    End Property 

End Class

Example 6 � Searching a Typed List Using LINQ to Objects

In this example, a typed list is created (as in the previous example), populated, and then queried using LINQ to Objects.  In this case, the query includes a where clause that only returns matches were the part description begins with the letter "S":

        Dim list = From p In parts _

                   Where p.PartDescription.StartsWith("S") _

                   Order By p.PartNumber Ascending _

                   Select p

 

        Dim sb As New StringBuilder()

        Dim pt As Parts

 

        For Each pt In list

            sb.Append(pt.PartNumber.ToString() + ": " _

            + pt.PartDescription.ToString() + _

            Environment.NewLine)

        Next
        MessageBox.Show(sb.ToString(), "Parts List")

 

 

Figure 10:  Matching Parts Query Results

Example 7 � Searching a Typed List Using LINQ to Objects and Returning a Single Result

In this example, a typed list is created (as in the previous example), populated, and then queried using LINQ to Objects.  In this case, returns a single result of type "Parts":

 Dim matchingPart = (From m In list _

                            Where m.PartNumber.Equals(5) _

                            Select m).Single()

 

        MessageBox.Show(matchingPart.PartDescription, "Matching Part")

The results of this query are shown in the next figure.

Figure 11:  Returning a Single Result

The preceding examples were intended to provide a simple overview as to how to conduct some basic queries against collections using LINQ to Objects; there are certainly a great number of more complex operations that can be executed using similar procedures (grouping, joins and selects into a new custom type, etc.).

Getting Started

There is a single solution included with this download, the solution contains a Win Forms project called "LinqToObjectsVB"; this project contains two forms (the main form (frmContactBook) and a form used to display the total list of contacts (frmFullList), a serializable class called 'Contact' (used to contain contact related data), and a class entitled, 'Serializer' which contains two static methods used to serialize and deserialize the contact data (writing it to and reading it from a file) . 

If you open the attached project into Visual Studio 2008; you should see the following in the solution explorer:

Figure 12:  Solution Explorer

Code:  Contact.vb

The Contact class is the container class used to store all of the contact related data used in the application.  Whilst this demonstration uses contact data, this could easily be replaced with something more useful to you.

The class begins with the normal and default imports:

Imports System

Imports System.Collections.Generic

Imports System.Linq

Imports System.Text

The next section contains class declaration.  Note that the class is declared as serializable; the serializable attribute indicates that the class can be serialized.

<Serializable()> _

Public Class Contact

The region defined in the class declares the member variables used internally by the class; any member variables exposed externally are made accessible through public properties.

#Region "Member Variables"

 

    Private mId As System.Guid

    Private mFirstName As String

    Private mMiddleName As String

    Private mLastName As String

    Private mStreet As String

    Private mCity As String

    Private mState As String

    Private mZip As String

    Private mEmail As String

    Private mHousePhone As String

    Private mWorkPhone As String

    Private mCellPhone As String

    Private mFax As String

 

#End Region

The next region of code in the class contains the constructors.  Two constructors are defined; a default constructor that creates a new instance of the class and assigns it an internal ID (as a Guid).  The second constructor accepts an ID as an argument and sets the contact's internal ID to that value.

#Region "Constructor"

 

    Public Sub New()

        mId = Guid.NewGuid()

    End Sub

 

 

    Public Sub New(ByVal ID As System.Guid)

        mId = ID

    End Sub

 

#End Region

The last bit of the code in this class is contained within the properties region; this region contains all of the properties defined to access the member variables.  Note that since the ID value is always set by the constructor, the property does not provide a public interface to set the Guid to a new value.

#Region "Properties"

 

    Public Property FirstName() As String

        Get

            Return mFirstName

        End Get

        Set(ByVal value As String)

            mFirstName = value

        End Set

    End Property 

 

    Public Property MiddleName() As String

        Get

            Return mMiddleName

        End Get

        Set(ByVal value As String)

            mMiddleName = value

        End Set

    End Property 

 

    Public Property LastName() As String

        Get

            Return mLastName

        End Get

        Set(ByVal value As String)

            mLastName = value

        End Set

    End Property 

 

    Public Property Street() As String

        Get

            Return mStreet

        End Get

        Set(ByVal value As String)

            mStreet = value

        End Set

    End Property 

 

    Public Property City() As String

        Get

            Return mCity

        End Get

        Set(ByVal value As String)

            mCity = value

        End Set

    End Property 

 

    Public Property State() As String

        Get

            Return mState

        End Get

        Set(ByVal value As String)

            mState = value

        End Set

    End Property 

 

    Public Property ZipCode() As String

        Get

            Return mZip

        End Get

        Set(ByVal value As String)

            mZip = value

        End Set

    End Property 

 

    Public Property Email() As String

        Get

            Return mEmail

        End Get

        Set(ByVal value As String)

            mEmail = value

        End Set

    End Property 

 

    Public Property HousePhone() As String

        Get

            Return mHousePhone

        End Get

        Set(ByVal value As String)

            mHousePhone = value

        End Set

    End Property 

 

    Public Property WorkPhone() As String

        Get

            Return mWorkPhone

        End Get

        Set(ByVal value As String)

            mWorkPhone = value

        End Set

    End Property 

 

    Public Property CellPhone() As String

        Get

            Return mCellPhone

        End Get

        Set(ByVal value As String)

            mCellPhone = value

        End Set

    End Property 

 

    Public Property Fax() As String

        Get

            Return mFax

        End Get

        Set(ByVal value As String)

            mFax = value

        End Set

    End Property 

 

#End Region 

End Class

That concludes the description of the 'Contact' class.

Code:  Main Application Form (frmContactBook.vb)

The is the main form of the application; much of the code provides the framework for the application and does not really pertain to LINQ to Objects, however, all of the code will be described herein to provide a proper context.

The contact application's main form contains the following controls: 

  • Menu
    • File
      • New
      • Open 
      • Save
      • Save As
      • Exit
    • Contacts
      • Add Contact
      • Remove Contact
      • List All Contacts
  • Toolbar
    • Add
    • Remove
    • Find  by Last Name
    • Save Data
    • Navigate to Previous Contact
    • Navigate to Next Bird Contact
    • Exit Application
  • Split Container Left Hand Side
    • Alphabet List
    • Alphabetized Names List
  • Split Container Right Hand Side
  • First name text box control
  • Middle name text box control
  • Last name text box control
  • Street text box control
  • City text box control
  • State text box control
  • Zip code text box control
  • Home phone number text box control
  • Work phone number text box control
  • Cell number text box control
  • Fax number text box control
  • Email address text box control

Figure 13:  frmContactBook.vb

The class begins with the normal and default imports:

Imports System

Imports System.Collections.Generic

Imports System.ComponentModel

Imports System.Data

Imports System.Drawing

Imports System.Linq

Imports System.Text

Imports System.Windows.Forms

The next section contains the class declaration. 

Public Class frmContactBook

The next region defined in the class declares the member variables used internally by the class; any member variables exposed externally are made accessible through public properties.  The comment adjacent to each declaration describes its purpose.

#Region "Member Variables"

 

    Private contacts As New List(Of Contact)    ' create a typed list of contacts

    Private currentContact As Contact           ' create a single contact instance

    Private currentPosition As Integer          ' used to hold current position

    Private currentFilePath As String           ' file path to current contact file

    Private dirtyForm As Boolean                ' keep track of dirty forms

 

#End Region

The next region of code in the class contains the constructor.  Upon initialization, the application creates a new contact data list, creates a new contact data object, sets the current position indicator to zero, and sets the dirty form Boolean to false.

#Region "Constructor" 

 

    ''' <summary>

    ''' Constructor - create a new instance of

    ''' the contact list and setup the local

    ''' member variables

    ''' </summary>

    ''' <remarks></remarks>

    Public Sub New()

 

        ' This call is required by the Windows Form Designer.

        InitializeComponent()

 

        ' Add any initialization after the InitializeComponent() call.

 

        contacts = New List(Of Contact)

        currentContact = New Contact()

        contacts.Add(currentContact)

        currentPosition = 0

        dirtyForm = False

 

    End Sub 

#End Region

The next code region is called 'Toolstrip Event Handlers'; the first event handler in this region is the click event handler for the Add button; this method merely calls the menu control's click event handler and the code contained in that event handler adds a new contact to the current contact data.

#Region "Toolstrip Event Handlers" 

 

    ''' <summary>

    ''' Add a new contact to the current contact list

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub tsbAddRecord_Click(ByVal sender As System.Object, _

                                   ByVal e As System.EventArgs) _

                                   Handles tsbAddRecord.Click

 

        Me.addToolStripMenuItem_Click(Me, New EventArgs()) 

    End Sub

The next click event handler is used remove the current contact from the contact list when the user clicks the toolstrip's remove record button; again, this method merely calls the matching menu item function.

    ''' <summary>

    ''' Remove the current contact from the contact list

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub tsbRemoveRecord_Click(ByVal sender As System.Object, _

                                      ByVal e As System.EventArgs) _

                                      Handles tsbRemoveRecord.Click

 

        Me.removeToolStripMenuItem_Click(Me, New EventArgs())

 

    End Sub

The next handler is used to search for a specific contact using the contact's last name.  The code uses a LINQ to Objects query in order to find the first instance of a matching contact with that last name.  The handler uses the search term text box control on the toolstrip to capture the last name and it uses the search button to execute the search.  The code is annotated to describe what is going on in this method.

    ''' <summary>

    ''' Find a contact by searching the list

    ''' for a matching last name

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub tsbFindContact_Click(ByVal sender As System.Object, _

                                     ByVal e As System.EventArgs) _

                                     Handles tsbFindContact.Click

 

        ' return if the search term was not provided

        If (String.IsNullOrEmpty(tspSearchTerm.Text)) Then

 

            MessageBox.Show("Enter a last name in the space proved.", _

                            "Missing Search Term")

            Return

 

        End If

 

 

        Try

            ' using linq to objects query to get first matching name

            Dim foundGuy = _

                        (From contact In contacts _

                         Where contact.LastName = tspSearchTerm.Text _

                         Select contact).FirstOrDefault()

 

            ' set the current contact to the found contact

            currentContact = foundGuy

            currentPosition = contacts.IndexOf(currentContact)

 

            ' update the display by loading the

            ' found contact

            LoadCurrentContact()

 

            ' clear the search term textbox and return

            tspSearchTerm.Text = String.Empty

            Return

 

        Catch

 

            MessageBox.Show("No matches were found", "Search Complete")

 

        End Try

 

    End Sub

The next handler saves the current contact list, this handler just calls the matching menu click event handler.

    ''' <summary>

    ''' Save the current contact list

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub tsbSave_Click(ByVal sender As System.Object, _

                              ByVal e As System.EventArgs) _

                              Handles tsbSave.Click

 

        Me.saveStripMenuItem_Click(Me, New EventArgs())

 

    End Sub

The next handler is used to navigate back one contact from the current position of the displayed contact.  If the contact as at the lower limit; the button click is ignored.

    ''' <summary>

    ''' Navigate back to the previous record

    ''' if not at the lower limit

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub tsbNavBack_Click(ByVal sender As System.Object, _

                                 ByVal e As System.EventArgs) _

                                 Handles tsbNavBack.Click

 

        ' capture form changes and plug them

        ' into the current contact before

        ' navigating off the contact

        SaveCurrentContact()

 

        ' don't exceed the left limit

        If (currentPosition <> 0) Then

            currentPosition -= 1

            currentContact = contacts(currentPosition)

            LoadCurrentContact()

        End If

 

    End Sub

The next handler is used to navigate forward one contact from the current position of the displayed contact.  If the contact as at the upper limit; the button click is ignored.

    ''' <summary>

    ''' Navigate to the next record if

    ''' not at the upper limit

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub tsbNavForward_Click(ByVal sender As System.Object, _

                                    ByVal e As System.EventArgs) _

                                    Handles tsbNavForward.Click

 

        ' capture form changes and plug them

        ' into the current contact before

        ' navigating off the contact

        SaveCurrentContact()

 

        ' don't exceed the right limit

        If (currentPosition < contacts.Count - 1) Then

            currentPosition += 1

            currentContact = contacts(currentPosition)

            LoadCurrentContact()

        End If

    End Sub

The next handler is used to exit the application.  This handler merely calls the matching menu item click event handler.

    ''' <summary>

    ''' Exit the application

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub tsbExit_Click(ByVal sender As System.Object, _

                              ByVal e As System.EventArgs) _

                              Handles tsbExit.Click

 

        Me.exitToolStripMenuItem_Click(Me, New EventArgs())

 

    End Sub 

 

#End Region

The next region contains the menu item click event handlers.  The next menu item click event handler creates a new contact list; before following through with the creation of the new contact list, this handler checks to see if the current form is dirty to allow the user the opportunity to save before closing the current list.  Following that, the contact list is replaced with a new contact list and the form's controls are cleared.

#Region "Menu Item Click Event Handler" 

 

    ''' <summary>

    ''' Create a new contact list and clear

    ''' the contact form

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub newToolStripMenuItem_Click(ByVal sender As System.Object, _

                                           ByVal e As System.EventArgs) _

                                           Handles newToolStripMenuItem.Click

 

        ' check to see if the form has been editted

        If dirtyForm = True Then

 

          If (MessageBox.Show(Me, "You have not saved the current contact data; " + _

                             "would you like to save before starting a new " + _

                             "contact database?", "Save Current Data"

                MessageBoxButtons.YesNo) = _

                System.Windows.Forms.DialogResult.Yes) Then

 

                ' display the save dialog if the contact list is dirty

                saveAsMenuItem_Click(Me, New EventArgs())

 

            End If

 

        Else

 

            ' discard the contact list and

            ' start new document

            contacts = New List(Of Contact)

            ClearScreen()

 

        End If 

    End Sub       

The next event handler is used to open a contacts file.  Again, the handler checks to a dirty form and provides the user with an opportunity to save if the form is dirty.  A separate open method is called to handle the actually file opening operation.

    ''' <summary>

    ''' Open an existing contact file

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub openToolStripMenuItem_Click(ByVal sender As System.Object, _

                                            ByVal e As System.EventArgs) _

                                            Handles openToolStripMenuItem.Click

 

        ' give the user an opportunity to save the current

        ' contact list if the data has been edited

        If dirtyForm = True Then

 

          If (MessageBox.Show(Me, "You have not saved the current contact data; " + _

                             "would you like to save before opening a different " + _

                             "contact database?", "Save Current Data",

                MessageBoxButtons.YesNo) = _

                System.Windows.Forms.DialogResult.Yes) Then

 

                saveAsMenuItem_Click(Me, New EventArgs())

 

            End If

 

        Else

 

            ' call the open function to open the file

            Open()

 

        End If 

    End Sub               

The save menu item is used to save the current contacts file to disk; the function first calls a "SaveCurrentContact" which is used to save the current contact to the current contact data list.  Next, the function uses the save file dialog to capture a file name if none is currently set to the "currentFilePath" variable, or, if the variable is set, it saves the file using that file path.  The file is actually saved to disk when the call to serialize the file is made.

    ''' <summary>

    ''' Save the current contact list

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub saveStripMenuItem_Click(ByVal sender As System.Object, _

                                        ByVal e As System.EventArgs) _

                                        Handles saveStripMenuItem.Click

 

        ' save the current form data to the list

        SaveCurrentContact()

 

        ' if the file path is not set, open the

        ' save file dialog

        If String.IsNullOrEmpty(currentFilePath) Then

 

            Dim SaveFileDialog1 As New SaveFileDialog()

 

            Try

                SaveFileDialog1.Title = "Save CON Document"

                SaveFileDialog1.Filter = "CON Documents (*.con)|*.con"

 

                If (SaveFileDialog1.ShowDialog() = _

                    System.Windows.Forms.DialogResult.Cancel) Then

                    Return

                End If

            Catch

                Return

            End Try

 

            currentFilePath = SaveFileDialog1.FileName

 

        End If

 

        ' make sure the file path is not empty

        If String.IsNullOrEmpty(currentFilePath) Then

            Return

        End If

 

        ' persist the contacts file to disk

        Serializer.Serialize(currentFilePath, contacts)

 

        ' tell the user the file was saved

        MessageBox.Show("File " + currentFilePath + " saved.", "File Saved.")

 

        ' everything is saved, set the dirtyform

        ' boolean to false

        dirtyForm = False 

    End Sub

The next bit of code us used to support the "Save As" menu item; the call is similar to the previous save method but straight opens the Save File dialog box to permit the user to name or rename the file.

    ''' <summary>

    ''' Save the current contact data as a file

    ''' under a new name

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub saveAsMenuItem_Click(ByVal sender As System.Object, _

                                     ByVal e As System.EventArgs) _

                                     Handles saveAsMenuItem.Click

 

        ' save the current form data to the contact list

        SaveCurrentContact()

 

        ' create and show the save file dialog

        Dim SaveFileDialog1 As New SaveFileDialog()

 

        Try

            SaveFileDialog1.Title = "Save CON Document As"

            SaveFileDialog1.Filter = "CON Documents (*.con)|*.con"

 

            If (SaveFileDialog1.ShowDialog() = _

                System.Windows.Forms.DialogResult.Cancel) Then

                Return

            End If

        Catch

            Return

        End Try

 

        currentFilePath = SaveFileDialog1.FileName

 

        ' make sure the file path is set

        If String.IsNullOrEmpty(currentFilePath) Then

            Return

        End If

 

        ' persist the contacts file to disk

        Serializer.Serialize(currentFilePath, contacts)

 

        ' tell the user the file was saved

        MessageBox.Show("File " + currentFilePath + " saved.", "File Saved.")

 

        ' everything is saved, set the dirtyform

        ' boolean to false

        dirtyForm = False 

    End Sub

The next method exits the application but checks the dirty form Boolean prior to exiting to give the user a chance to save their edits.

    ''' <summary>

    ''' Exit the application

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub exitToolStripMenuItem_Click(ByVal sender As System.Object, _

                                            ByVal e As System.EventArgs) _

                                            Handles exitToolStripMenuItem.Click

 

        If dirtyForm = True Then

 

          If (MessageBox.Show(Me, "You have not saved the current contact data; " + _

                             "would you like to save before exiting?", "Save Current

                             Data", _

                             MessageBoxButtons.YesNo) =

                             System.Windows.Forms.DialogResult.Yes) Then

 

                tsbSave_Click(Me, New EventArgs())

 

            End If

 

        Else

 

            Application.Exit()

 

        End If 

    End Sub

The next method is used to add a new contact to the current list of contacts; this method saves the current contact to the open list of contacts, creates a new contact and adds it to the list of contacts, clears the form, and marks the form as dirty:

    ''' <summary>

    ''' Add a new contact to the current

    ''' contact list

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub addToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As

    System.EventArgs) Handles addToolStripMenuItem.Click

 

        SaveCurrentContact()

        currentContact = New Contact()

        contacts.Add(currentContact)

        ClearScreen()

        dirtyForm = True 

    End Sub

The next method removes the current contact from the list and updates the display.

    ''' <summary>

    ''' Remove the current contact from the

    ''' contact list and update the display

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub removeToolStripMenuItem_Click(ByVal sender As System.Object, _

                                              ByVal e As System.EventArgs) _

                                              Handles removeToolStripMenuItem.Click

 

        ' make sure there are records

        If contacts.Count = 0 Then

 

            ' remove the current record

            contacts.Remove(currentContact)

 

            ' check to see if the current

            ' position is at the limit

            ' and move up or down

            ' as required

            If (currentPosition = 0) Then

 

                currentPosition += 1

 

            End If

 

        Else

 

            currentPosition -= 1

 

            ' reload the current contact

            ' from the new position

            currentContact = contacts(currentPosition)

            LoadCurrentContact()

 

            ' dirty the form since a

            ' record was removed

            dirtyForm = True

 

        End If 

    End Sub

The next method is used to open a separate form displaying all of the contacts in a data grid view control.  This method constructs and ordered list and passes it to a new instant of the "frmFullList" class which binds the grid control to the list in its constructor.

    ''' <summary>

    ''' Display a list of all the contacts in a data grid

    ''' view control

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub listAllContactsToolStripMenuItem_Click(ByVal sender As System.Object,

                                                       ByVal e As System.EventArgs) _

                                                       Handles

                                               listAllContactsToolStripMenuItem.Click

 

        ' use linq to objects to create a list of contacts

        ' ordered by the contact's last name, first name,

        ' and middle name

        Dim orderedCons = _

            (From contact In contacts _

             Order By contact.LastName Ascending, _

             contact.FirstName Ascending, _

             contact.MiddleName Ascending _

             Select contact)

 

        ' create an instance of the full list form and pass it's

        ' constructor the list converted to a List

        Dim f As New frmFullList(orderedCons.ToList())

        f.Show()

 

    End Sub 

#End Region

The next region contains a garbage can collection of other methods maintained in a region entitled "Housekeeping":

#Region "Housekeeping"

The first method contained in this section is used to clear all of the text boxes used to display contact information.  This is called anytime the current contact is changed to prevent remnants of one contact appearing the display of a replacement contact.

    ''' <summary>

    ''' Clear all of the fields in the form

    ''' </summary>

    ''' <remarks></remarks>

    Private Sub ClearScreen()

 

        txtFirstName.Text = String.Empty

        txtMiddleName.Text = String.Empty

        txtLastName.Text = String.Empty

        txtStreet.Text = String.Empty

        txtCity.Text = String.Empty

        txtState.Text = String.Empty

        txtZipCode.Text = String.Empty

        txtHousePhone.Text = String.Empty

        txtWorkPhone.Text = String.Empty

        txtCellPhone.Text = String.Empty

        txtFax.Text = String.Empty

        txtEmailAddress.Text = String.Empty

 

        tslViewWho.Text = String.Empty

 

    End Sub

The next method is used to load the information contained in the current contact into the controls used to display contact information.

    ''' <summary>

    ''' Load the current contact into the form fields

    ''' </summary>

    ''' <remarks></remarks>

    Private Sub LoadCurrentContact()

 

        ' update the form fields

        txtFirstName.Text = currentContact.FirstName

        txtMiddleName.Text = currentContact.MiddleName

        txtLastName.Text = currentContact.LastName

        txtStreet.Text = currentContact.Street

        txtCity.Text = currentContact.City

        txtState.Text = currentContact.State

        txtZipCode.Text = currentContact.ZipCode

        txtHousePhone.Text = currentContact.HousePhone

        txtWorkPhone.Text = currentContact.WorkPhone

        txtCellPhone.Text = currentContact.CellPhone

        txtFax.Text = currentContact.Fax

        txtEmailAddress.Text = currentContact.Email

 

        ' display the current user in the status bar

        tslViewWho.Text = "Now Viewing " + txtFirstName.Text + " " + txtLastName.Text 

    End Sub

The next method captures all of the information currently on the form for the current contact and writes it into the current contact's properties.  This is called whenever a contact is changed so that all edits to an existing contact are held within the local list until it can be written to disk.  The method further updates the order of the contacts and updates the contact list and displayed contact.

    ''' <summary>

    ''' Save the form data to the current

    ''' contact, reorder the contact list,

    ''' and update the display

    ''' </summary>

    ''' <remarks></remarks>

    Private Sub SaveCurrentContact()

 

        If (Not String.IsNullOrEmpty(txtFirstName.Text) And _

            (Not String.IsNullOrEmpty(txtLastName.Text))) Then

 

            Try

 

                ' get all of the textbox values and

                ' plug them into the current contact object

                currentContact.FirstName = txtFirstName.Text

                currentContact.MiddleName = txtMiddleName.Text

                currentContact.LastName = txtLastName.Text

                currentContact.Street = txtStreet.Text

                currentContact.City = txtCity.Text

                currentContact.State = txtState.Text

                currentContact.ZipCode = txtZipCode.Text

                currentContact.HousePhone = txtHousePhone.Text

                currentContact.WorkPhone = txtWorkPhone.Text

                currentContact.CellPhone = txtCellPhone.Text

                currentContact.Fax = txtFax.Text

                currentContact.Email = txtEmailAddress.Text

 

 

                ' reorder the contacts by last, first, and

                ' middle name to keep everything in correct

                ' alphabetical order

                Dim orderedContacts = _

                        (From contact In contacts _

                         Order By contact.LastName Ascending, _

                         contact.FirstName Ascending, _

                         contact.MiddleName Ascending _

                         Select contact).ToList()

 

                ' set the contacts list to the newly

                ' ordered list

                contacts = orderedContacts

 

                ' update the current position index value

                currentPosition = contacts.IndexOf(currentContact)

 

                ' reload the current contact

                LoadCurrentContact()

 

            Catch ex As Exception

 

                MessageBox.Show(ex.Message, "Error")

 

            End Try

 

        End If 

    End Sub

The next method is used to open and deserialize an existing contact file, making it available for edit and viewing within the application.

    ''' <summary>

    ''' Open an existing contacts file

    ''' </summary>

    ''' <remarks></remarks>

    Public Sub Open()

 

        Dim OpenFileDialog1 As New OpenFileDialog()

        OpenFileDialog1.Title = "Open con Document"

        OpenFileDialog1.Filter = "CON Documents (*.con)|*.con"

 

        If OpenFileDialog1.ShowDialog() = System.Windows.Forms.DialogResult.Cancel

        Then

            Return

        End If

 

        currentFilePath = OpenFileDialog1.FileName

 

        If String.IsNullOrEmpty(currentFilePath) Then

            Return

        End If

 

        If System.IO.File.Exists(currentFilePath) = False Then

            Return

        End If

 

        ' deserialize file content into contacts

        ' list to make it available to the application

        contacts = Serializer.Deserialize(currentFilePath)

 

        ' alphabetize the contact list

        ' by last, first, and middle name and

        ' push the results into a List

        Dim orderedContacts = _

                    (From contact In contacts _

                     Order By contact.LastName Ascending, _

                      contact.FirstName Ascending, _

                      contact.MiddleName Ascending _

                     Select contact).ToList()

 

        ' set the contacts list to the newly

        ' ordered list

        contacts = orderedContacts

 

        ' Load contacts at position zero

        ' if contacts list is not empty

        If contacts.Count > 0 Then

            currentContact = contacts.ElementAt(0)

            LoadCurrentContact()

            dirtyForm = False

        End If 

 

    End Sub 

#End Region

The final region in this form class is used to handle the listbox control events.  These controls are used to provide a Rolodex sort of functionality to the application.  The listbox controls are loaded into the left hand split panel's panel.  The top listbox control displays all of the letters in the alphabet whilst the lower listbox control is used to display all matching last names beginning with the letter selected in the upper listbox.

#Region "Listbox Event Handlers"

 

The first function handles the selected index changed event for the upper listbox containing all of the letters of the alphabet.  When a new letter is selected, this method uses a simple LINQ to Objects query to find all contacts with last names beginning with the selected letter.  The lower listbox is then cleared and then the matches are then formatted into a string showing the contact's last name, first name, and middle name and each formatted string is then added to the lower listbox control.
 

    ''' <summary>

    ''' Find all last names starting with the selected letter

    ''' and display that list of matching names in the names

    ''' list box

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub lstAlphas_SelectedIndexChanged(ByVal sender As System.Object, _

                                               ByVal e As System.EventArgs) _

                                               Handles lstAlphas.SelectedIndexChanged

 

        ' store the selected letter as a local variable

        Dim alpha As String = lstAlphas.SelectedItem.ToString()

 

        ' make sure the contact list is not empty

        If contacts.Count > 0 Then

 

            Try

 

                ' use linq to objects query to find

                ' last names matching the selected

                ' letter of the alphabet

                Dim alphaGroup = _

                    From contact In contacts _

                    Where contact.LastName.ToUpper().StartsWith(alpha) _

                    Select contact

 

                ' clear out any names from the

                ' existing list

                lstNames.Items.Clear()

 

                ' add the short list of matching

                ' names to the list box

                Dim con As Contact

                For Each con In alphaGroup

                    lstNames.Items.Add(con.LastName + ", " + _

                        con.FirstName + " " + con.MiddleName)

                Next

 

                ' if no matches were found, tell the user

                ' with a note in the box

                If (alphaGroup.Count < 1) Then

                    lstNames.Items.Clear()

                    lstNames.Items.Add("No matches were found")

                End If

 

            Catch

                lstNames.Items.Clear()

                lstNames.Items.Add("No matches were found")

            End Try

 

        End If 

    End Sub

Then the names listbox selected index changed event is handled in the next block of code.  In it, the name string (Last name, first name, middle name) is parsed and used in a LINQ to Objects query used to return a list of all matching names;  the first found name is displayed in the contact form and the index position is updated to support the list navigation.

    ''' <summary>

    ''' Display the selected contact's information in the

    ''' contact form's fields

    ''' </summary>

    ''' <param name="sender"></param>

    ''' <param name="e"></param>

    ''' <remarks></remarks>

    Private Sub lstNames_SelectedIndexChanged(ByVal sender As System.Object, _

                                                  ByVal e As System.EventArgs) _

                                                  Handles lstNames.SelectedIndexChanged

 

        ' if there were no matches found, return from this function

        If (lstNames.SelectedItem.ToString().Trim() = "No matches were found") Then

            Return

        End If

 

        ' variables to hold parts of the name as search terms

        Dim first As String = String.Empty

        Dim middle As String = String.Empty

        Dim last As String = String.Empty

 

        ' get the last name

        Dim arr() As String = lstNames.SelectedItem.ToString().Trim().Split(",")

        last = arr(0).Trim()

 

        ' get the first name

        Dim arr2() As String = arr(1).ToString().Trim().Split(" ")

        first = arr2(0).Trim()

 

        ' get the middle name

        Try

            middle = arr2(1).Trim()

        Catch

            ' no middle name

        End Try

 

        Try

            ' using linq to objects query to get a collection of matching names

            ' when all three names match

            Dim foundGuy = _

                        (From contact In contacts _

                         Where contact.FirstName.Equals(first) And _

                               contact.LastName.Equals(last) And _

                               contact.MiddleName.Equals(middle) _

                         Select contact).FirstOrDefault()

 

            ' set the current contact to the first found

            ' contact

            currentContact = foundGuy

 

            ' update the index position used to maintain

            ' the current position within the list

            currentPosition = contacts.IndexOf(currentContact)

 

            ' reload the current contact and return

            LoadCurrentContact()

            Return

 

        Catch ex As Exception

            MessageBox.Show(ex.Message, "Error Encountered")

        End Try

 

    End Sub 

 

#End Region 

 

End Class

Code:  frmFullList.vb

This form class contains a data grid view control and a constructor which accepts a contact list (List(Of Contact)) as an argument.  Upon initialization the list is bound to the data grid view control.
Changes made by edits in the grid are maintained in the contact list.

There is not much code; it is presented here in its entirety:

Imports System

Imports System.Collections.Generic

Imports System.ComponentModel

Imports System.Data

Imports System.Drawing

Imports System.Linq

Imports System.Text

Imports System.Windows.Forms 

 

Public Class frmFullList

 

    Public Sub New(ByVal cons As List(Of Contact))

 

        ' This call is required by the Windows Form Designer.

        InitializeComponent()

 

        ' display the contact list in the

        ' data grid view control

        dgvFullList.DataSource = cons

 

     End Sub 

End Class

Summary

The article shows some simple examples of LINQ to Objects queries used in support of a sample application.  LINQ to Objects may be used to generate more complex queries than are shown in the example, however, those demonstrated herein are representative of some of the more common tasks that one might choose to do within a similar application.  Much of the code in the demonstration project was provided as a framework for the application and was necessary to create an environment useful for testing some simple LINQ to Objects based queries.