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.