This chapter's project code adds two major features to the application. The first task adds holidays to the system. When a patron checks out a book or other library item, the due date is automatically calculated based on a number of days stored in the CodeMediaType.CheckoutDays database field. But what happens if that calculated date is a holiday, and the library is closed? The patron might not be able to return the book until the next day, and would incur a fine. This fine, though small, could start a chain reaction in the patron's life that would lead to poverty, despair, and an addiction to soap operas. Fortunately, this can all be avoided by adding a list of holidays to the project. If an item's return date falls on a documented holiday, the program adjusts the date forward until it finds a non-holiday date.

In the second part of the project code, we finally add what many consider to be the heart of a library system: the lookup of books and other library items by patrons.

Project Access

Load the "Chapter 16 (Before) Code" project, either through the New Project templates, or by accessing the project directly from the installation directory. To see the code in its final form, load "Chapter 16 (After) Code" instead.

Managing Holidays

As a small, stand-alone application that fully manages its own data, there isn't necessarily a pressing need for generics in the Library application. However, generics provide more advantages than just limiting the types of data stored in a class or collection. They also enhance data conversion and IntelliSense support, because Visual Basic can tell immediately, for instance, what type of data will appear in a collection.

We'll store all holidays managed by the Library Project in the Holiday database table. The contents of this table will seldom change, and will be frequently accessed during the checkout process. To speed things up, we'll cache the data inside of the application. And to simplify management of that cache, we'll store the holidays in a generic collection.

First, let's create the class that holds a single holiday entry. Add a new class to the project through the Project Add Class menu command, and give it the name HolidaySet.vb. The familiar structure of an empty class appears.

Public Class HolidaySet End Class 

The Holiday database table includes two main fields used in calculating holidays: EntryType and EntryDetail. Let's store these as members of the class, and add a flag that ensures the entry is valid.

Insert Snippet

Insert Chapter 16, Snippet Item 1.

Private HolidayType As String Private HolidayDetail As String Private IsValid As Boolean 

We'll populate these private members through the class constructor.

Insert Snippet

Insert Chapter 16, Snippet Item 2.

Public Sub New(ByVal entryType As String, _       ByVal entryDetail As String)    ' ----- Create a new holiday entry instance.    HolidayType = Left(Trim(UCase(entryType)), 1)    HolidayDetail = entryDetail    ' ----- See if the details are valid.    IsValid = True    Select Case HolidayType       Case "A"          ' ----- The detail should be in mm/dd format.          IsValid = IsDate(entryDetail & "/2004")       Case "E"          ' ----- The detail is a number from 1 to 7.          If (Val(entryDetail) < 1) Or _             (Val(entryDetail) > 7) Then IsValid = False       Case "O"          ' ----- The detail should be a valid date.          IsValid = IsDate(entryDetail)       Case Else          ' ---- Invalid. This should never happen.          IsValid = False    End Select End Sub 

Clearly, the holiday entries have a coding system all their own, and it wouldn't be fair to force code elsewhere in the application to deal with all of the complexities of holiday date comparisons. So let's add a public method to the class that indicates whether or not a given date matches the holiday stored in an instance.

Insert Snippet

Insert Chapter 16, Snippet Item 3.

Public Function IsHoliday(ByVal whatDate As Date) As Boolean    ' ----- Given a date, see if it matches the entry    '       type in this instance.    Dim buildDate As String    ' ----- If this record is invalid, then it is never a    '       holiday match.    If (IsValid = False) Then Return False    Select Case HolidayType       Case "A"          ' ----- Annual.          buildDate = HolidayDetail & "/" & Year(whatDate)          If (IsDate(buildDate)) Then             Return CBool(CDate(buildDate) = whatDate)          Else             ' ----- Must be 2/29 on a non-leap-year.             Return False          End If       Case "E"          ' ----- Day of the week.          Return CBool(Val(HolidayDetail) = _             Weekday(whatDate, FirstDayOfWeek.Sunday))       Case "O"          ' ----- See if this is an exact one-time match.          Return CBool(CDate(HolidayDetail) = whatDate)    End Select End Function 

We're done with that class. Now we just need a place to keep our cached holiday records. The System.Collections.Generic namespace includes a few different collection classes that we could use. Because the only thing we really need to do with the holidays once they are in the collection is scan through them, looking for matches, the standard no-frills list seems best. Its class name is List(Of T), and its primary feature, according to the .NET documentation, is that it lets you access members by index. That's fine.

Open up the General.vb file and find where the global variables appear, somewhere near the top. Then add a definition for the global collection that will store all of the holidays.

Insert Snippet

Insert Chapter 16, Snippet Item 4.

Public AllHolidays As Collections.Generic.List( _    Of Library.HolidaySet) 

There it is! There it is! The Of clause. This is a generic collection. Yeah! Okay, party's over; let's move on.

Locate the InitializeSystem method, still in the General.vb file, and add the code that will initialize the global holiday cache.

Insert Snippet

Insert Chapter 16, Snippet Item 5.

AllHolidays = New Collections.Generic.List(Of HolidaySet) 

That's it for infrastructure. Let's add some routines that access this generic list. We need a routine that will tell us, True or False, whether a given date (the planned due date of a library item) matches any of the holidays or not. Add the function IsHolidayDate to General.vb.

Insert Snippet

Insert Chapter 16, Snippet Item 6.

Public Function IsHolidayDate(ByVal whatDate As Date) _       As Boolean    ' ----- See if the given date is a holiday.    Dim oneHoliday As Library.HolidaySet    ' ----- Scan through the holidays, looking for a match.    For Each oneHoliday In AllHolidays       If (oneHoliday.IsHoliday(whatDate)) Then Return True    Next oneHoliday    ' ----- Not a holiday.    Return False End Function 

This routine, IsHolidayDate, shows where generics really come in handy. It's all in the For Each statement that the magic occurs. In a normal collection, we wouldn't be sure what type of items were stored in the collection, be they HolidaySet or String or Integer. Well, we would know because we are the developer, but Visual Basic plays dumb in this area, and assumes you mixed up the data types in one collection.

But because we tied the AllHolidays collection to the HolidaySet class using the Of HolidaySet clause, Visual Basic now understands that we are only going to store items of HolidaySet in the AllHolidays collection. That means that we don't have to explicitly convert items retrieved from the collection to the HolidaySet data type. If we weren't using a generic class, the code would look something like this.

Dim scanHoliday As System.Object Dim oneHoliday As Library.HolidaySet For Each scanHoliday In AllHolidays    oneHoliday = CType(scanHoliday, Library.HolidaySet)    If (oneHoliday.IsHoliday(whatDate)) Then Return True Loop 

Because non-generic collections boil everything down to System.Object, we would have to explicitly convert each collection object to HolidaySet using CType or similar conversion function. But with a generic collection, Visual Basic takes care of it for us.

We still need to cache the holidays from the database, so add a RefreshHolidays method to General.vb that does this.

Insert Snippet

Insert Chapter 16, Snippet Item 7.

Public Sub RefreshHolidays()    ' ----- Load in the list of holidays.    Dim sqlText As String    Dim dbInfo As SqlClient.SqlDataReader    Dim newHoliday As Library.HolidaySet    On Error GoTo ErrorHandler    ' ----- Clear the current list of holidays.    AllHolidays.Clear()    ' ----- Get the holidays from the database.    sqlText = "SELECT * FROM Holiday"    dbInfo = CreateReader(sqlText)    Do While dbInfo.Read       newHoliday = New Library.HolidaySet( _          CStr(dbInfo!EntryType), CStr(dbInfo!EntryDetail))       AllHolidays.Add(newHoliday)    Loop    dbInfo.Close()    Return ErrorHandler:    GeneralError("RefreshHolidays", Err.GetException())    On Error Resume Next    If Not (dbInfo Is Nothing) Then _       dbInfo.Close() : dbInfo = Nothing    Return End Sub 

You've seen a lot of code like this already, code that loads records from a database table into the program. I won't sport with your intelligence by explaining it to you line by line.

There are two places where we need to call RefreshHolidays: when the program first starts up, and later whenever changes are made to the list of holidays. We won't worry about other users changing the list; we'll just focus on when the local application updates the list. First, open the sometimes-hidden ApplicationEvents.vb file, and add this code to the MyApplication_Startup event handler, just after the existing call to LoadDatabaseSettings().

Insert Snippet

Insert Chapter 16, Snippet Item 8.


One down, and one to go. Open the MainForm.vb file, and locate the AdminLinkHolidays_LinkClicked event handler. This is the handler that lets the user edit the list of holidays. Add the same RefreshHolidays() line to the end of this routine.

Insert Snippet

Insert Chapter 16, Snippet Item 9.

' ----- Reload the holidays if they changed. RefreshHolidays() 

As you can see right in this routine, we already added the editor to manage the list of holidays. The only thing left to do is to actually access the holiday list when checking out items. We'll do that in a future chapter.

Looking Up Library Items

When we built the main Library form back in Chapter 7, "Windows Forms," we included fields that allowed a patron to search for library items. But that's about all we did; we didn't enable the fields or make them usable. We also didn't include any place to display a list of matching items. Let's complete those components in this chapter. We'll start with the matching items list.

I've added a form to the project named ItemLookup.vb that displays the results of a search for library items. It includes a few buttons at the top of the form, and three main display panels.

  1. PanelMatches. Contains a large list box that displays non-item matches. For instance, it displays a list of matching author or publisher names as searched for by the patron. When this panel appears, the patron selects a match from the MatchingGeneral list, and clicks the Lookup button to display items tied to that selected author, publisher, or other entry.

  2. PanelItems. Contains a large list box that displays items from the NamedItem database table. That is, it displays a list of library items matching some criteria. Selecting an item from the MatchingItems list and clicking the Lookup button displays the details of that item.

  3. PanelOneItem. Contains a WebBrowser control that displays details about a single library item. The detail content is built using standard HTML, and may contain links that return you to the PanelItems panel with a new set of matching items displayed. For instance, if you are viewing the details of an award-winning (one can hope) Visual Basic 2005 programming book and click on the publisher name for that item, the PanelItems panel appears, listing all items made by that publisher.

The form also includes a set of Back buttons (in the upper-left corner) that work like the Back button in your web browser, a Close button that returns to the main form, and a menu (BackMenu), used to support the Back button feature. Figure 16-2 shows the form with the PanelItems panel out in front, since it looks a little more interesting than the other two panels.

Figure 16-2. The panel of matching items, with column headings

The associated source code weighs in at around 1,000 lines, much of it focused on filling in the two list boxes and the HTML detail content. The search performed on the main form calls into this lookup form through the InitiateSearch method. The actual database search for matching items occurs in the PerformLookup method, which is called by InitiateSearch. PerformLookup includes distinct SQL queries for each type of search: title, author, subject, keyword, publisher, series, barcode, and some ID number searches, mostly for internal use. The type of search performed determines which of the three panels gets displayed (via the resultType variable). An author search displays PanelMatches with a list of matching author names; a title lookup displays matching items on the PanelItems panel. Here's the code that performs a lookup by publisher name based on a patron-supplied searchText.

sqlText = Trim(searchText) If (InStr(sqlText, "*") = 0) Then sqlText &= "*" sqlText = Replace(sqlText, "*", "%") sqlText = "SELECT ID, FullName FROM Publisher " & _    "WHERE FullName LIKE " & DBText(sqlText) & _    " ORDER BY FullName" resultType = "M" 

This code ensures that a wildcard character appears somewhere within the search text; if the user doesn't supply it, the code appends one to the end of searchText. Recall that SQL Server uses the percent (%) character for its wildcard, although the program lets the user enter the more familiar asterisk (*) character.

After processing this query through a data reader, the resultType = "M" flag moves the code to fill in the MatchingGeneral list.

MatchingGeneral.Items.Clear() Do While dbInfo.Read    ' ----- General list item.    MatchingGeneral.Items.Add(New ListItemData( _       CStr(dbInfo!FullName), CInt(dbInfo!ID))) Loop 

This is just more of the same code you've seen in previous chapters. It loads the ListBox control with ListItemData objects, each containing a display name and an ID number from the database. That's fine for a list with simple display requirements. But if you look back to Figure 16-2, it's clear we want something a little more interesting for the list of matching items. We want columns, and columns require reasonable data for each column.

To store this data, we'll make up a new class, called MatchingItemData, which works just like ListItemData, but has more data fields.

Private Class MatchingItemData    Public ItemID As Integer ' NamedItem.ID    Public Title As String    Public Subtitle As String    Public Author As String    Public MediaType As String    Public CallNumber As String    Public Overrides Function ToString() As String       ' ----- Build a simple display string.       If (Subtitle = "") Then          Return Title & ", by " & Author       Else          Return Title & ": " & Subtitle & ", by " & Author       End If    End Function End Class 

Because this class will be used only to display matching items on this form, I've made it a subordinate class within the larger ItemLookup form class. The ToString method outputs the text that appears in the list. We won't generate the actual columnar output until the next chapter. For now, we'll just display the title and author.

The PanelMatches and PanelItems panels each include a Lookup button that initiates a new call to PerformLookup based on the item selected in the list. The Lookup button on the PanelItems panel retrieves the selected MatchingItemData object from the list, and performs the new search.

Private Sub ActItemLookup_Click( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs) _       Handles ActItemLookup.Click    ' ----- Look up the item with the selected ID.    Dim itemID As Integer    ' ----- Ignore if no match is selected.    If (MatchingItems.SelectedIndex = -1) Then Return    itemID = CType(MatchingItems.SelectedItem, _       MatchingItemData).ItemID    ' ----- Perform the lookup.    If (PerformLookup(LookupMethods.ByDatabaseID, _       CStr(itemID), False) = False) Then Return    ' ----- Store the history.    AddLookupHistory(LookupMethods.ByDatabaseID, CStr(itemID)) End Sub 

The call to PerformLookup starts the process all over again.

Maintaining Search History

Let's say you have a patron with a lot of time on his hands, and he wants to look up the book War and Peace.

  • Starting from InitiateSearch and moving on to the PerformLookup code, the initial title search ("War and Peace") displays a list of matching titles on the PanelItems panel.

  • The patron locates the book in this list, and clicks the Lookup button, which calls the ActItemLookup_Click event handler.

  • This event handler in turn calls PerformLookup again, this time doing a precise lookup based on a database ID within the NamedItem table.

  • The detail of the item appears on the PanelOneItem panel. (I'll discuss how it's done later in this chapter.)

  • The detail includes a link to "Tolstoy, Leo," the long-suffering author of the book. When the patron clicks on this link, it initiates another call to PerformLookup, this time by author ID.

  • We're back to the PanelItems panel, viewing a list of books and other items by Tolstoy, assuming he had time to write anything else.

So the patron now has an experience with three search panels: (1) titles matching the name "War and Peace;" (2) the detail for the selected "War and Peace" item; and (3) items written by Leo Tolstoy. The history feature included in this form lets the patron return to any previous search page, just like the feature in your web browser.

It's possible that some of the searches performed could return hundreds of results. We don't want to store all of that content in memory, because it's possible the patron will never use the Back button. Instead, we will do just what your web browser does: store the minimum information needed to perform the query again. Your web browser maintains just the name and URL of visited paths in its "back" list. (File and image caching is not part of the history feature.) The ItemLookup.vb form needs to store only those values needed by PerformLookup to do the search again: the type of search, and the numeric or text criteria used in the search.

Patron history is accessed on a "last in, first out" basis. The most recent page viewed is the one the patron wants to see first when using the Back button. We discussed just such a last-in, first-out, or LIFO, structure earlier in this chapter: the stack. Each time the user views a panel, we'll make note of it, pushing just those values we will need later onto the stack. Later, when the user wants to view history, we will pop the most recent panel off the stack and update the display.

The ItemLookupHistory class, another subordinate class within the ItemLookup class, stores the values we need to manage history in the stack.

Private Class ItemLookupHistory    Public HistoryDisplay As String    Public LookupType As Library.LookupMethods    Public LookupData As String End Class 

HistoryDisplay provides a short display name to help the user scan through history. LookupType and LookupData are the values that get passed to PerformLookup. It's all nice and neat. To make things even neater, we'll use a generic stack for actual storage. It's declared as a field of the ItemLookup class.

Private LookupHistorySet As _    Collections.Generic.Stack(Of ItemLookupHistory) 

As the patron visits each panel, calls to the AddLookupHistory method populate the stack with each new visited item.

Private Sub AddLookupHistory( _       ByVal searchType As Library.LookupMethods, _       ByVal searchText As String)    ' ----- Add an item to the lookup history.    Dim newHistory As ItemLookupHistory    Dim displayText As String    ' ----- Build the text for display in the new item.    displayText = BuildDisplayText(searchType, searchText)    ' ----- Build the new history item.    newHistory = New ItemLookupHistory    newHistory.LookupType = searchType    newHistory.LookupData = searchText    newHistory.HistoryDisplay = displayText    LookupHistorySet.Push(newHistory)    ' ----- Update the back button.    RefreshBackButtons() End Sub 

Later, when the patron clicks one of the Back buttons, the BackMenuItems_Click event handler examines the history stack, and calls PerformLookup as needed. And because we stored the ItemLookupHistory objects in a generic stack, we don't have to specifically convert them from System.Object; the program just knows what data type they are.

Private Sub BackMenuItems_Click( _       ByVal sender As System.Object, _       ByVal e As System.EventArgs) _       Handles BackMenu1.Click, ..., BackMenu10.Click    ' ----- One of the back menu items was clicked.    Dim whichItem As Integer    Dim counter As Integer    Dim scanHistory As ItemLookupHistory    ' ----- Determine the clicked item.    whichItem = CInt(DigitsOnly(CType(sender, _       System.Windows.Forms.ToolStripMenuItem).Name))    If (whichItem >= LookupHistorySet.Count) Then Return    ' ----- Get rid of the in-between items.    For counter = 1 To whichItem       LookupHistorySet.Pop()    Next counter    ' ----- Perform a lookup as requested.    scanHistory = LookupHistorySet.Peek    If (PerformLookup(scanHistory.LookupType, _       scanHistory.LookupData, False) = False) Then Return    RefreshBackButtons() End Sub 

Showing Item Detail

The BuildHTMLAndLinks function builds the HTML content that appears on the PanelOneItem panel. This panel includes SingleItemDetail, a WebBrowser control included with .NET. It's basically a version of Internet Explorer that you embed in your applications. Normally, you supply it with a URL to display, but you can also provide custom content through the control's DocumentText property. The resultType = "S" branch of the PerformLookup method assigns this property with content returned from BuildHTMLAndLinks.

SingleItemDetail.DocumentText = BuildHTMLAndLinks(itemID) 

The content supplied by this routine is standard HTML, but with some specially crafted links that let the library program perform additional lookups based on the details of the displayed library item.

Most of the HTML is boilerplate, and it seems a shame to waste brain cells doing string concatenation just to include it. So instead, I stored much of the HTML as a text file resource through the Resources panel of the project properties. On that panel, I clicked the Add Resource button, selected Add New Text File (see Figure 16-3), and gave it the name ItemLookupBody.

Figure 16-3. Adding a new text file resource

In the text editor window that appeared, I added the following HTML content.

<html> <head> <style type="text/css"> body { font-family: "Arial"; } h1 { font-family: "Arial"; margin-top: 0px;    margin-bottom: 0px; font-size: 18pt; font-weight: bold; } h2 { font-family: "Arial"; margin-top: 20px;    margin-bottom: 0px; font-size: 15pt; font-weight: normal; } h3 { font-family: "Arial"; margin-top: 0px;    margin-bottom: 0px; font-size: 15pt; font-weight: normal;\    font-style: italic;  } p { margin-top: 2px; margin-bottom: 2px;    margin-left: 15px; font-family: "Arial"; font-size: 12pt; } table { border: solid black 1px; margin-left: 15px; } th { border: solid black 1px; background-color: black;    color: white; white-space: nowrap; text-align: left; } td { border: solid black 1px; white-space: nowrap; } a:visited { color: blue; } </style> </head> <body> 

If you're familiar with HTML, you recognize most of the content as an embedded Cascading Style Sheet. Its various formatting rules will bring a specific and consistent look and feel to the browser content that appears within the item lookup form. This is not a book on Cascading Style Sheets, but there are some good books at your local bookstore that can talk you through the rules and syntax if you're interested.

You can find the HTML content portion in the Solution Explorer, within the Resources branch. You've probably already noticed that the closing </body> and </html> tags aren't included. We'll attach those in the BuildHTMLAndLinks method. Because string concatenation is notoriously slow, I choose to use a StringBuilder class, a special string-like class that is custom designed for speed when repeatedly adding content to a base string. You attach content to the end of the StringBuilder using its Append and AppendLine methods, and retrieve the entire string through the standard ToString method.

We'll begin the content with the boilerplate HTML listed previously. Because we added it as a resource, it already appears in the My.Resources object under the name we gave it.

Dim detailBody As New System.Text.StringBuilder detailBody.Append(My.Resources.ItemLookupBody) 

Most of the code adds plain text to the detailBody string builder using its AppendLine method. Here's the code that adds the main book title.

sqlText = "SELECT Title, Subtitle FROM NamedItem " & _    "WHERE ID = " & itemID dbInfo = CreateReader(sqlText) dbInfo.Read() detailBody.AppendLine("<h1>" & _    HTMLEncode(CStr(dbInfo!Title)) & "</h1>") 

The HTMLEncode function, called in this block, is included in the ItemLookup class. It does some simple modification of special characters as required by HTML. It's called repeatedly throughout BuildHTMLAndLinks.

So that's the HTML, but what about the links? If I put a standard link to, say,, the embedded browser will jump to that page when the link is clicked. But that doesn't help me do database lookups. The WebBrowser control doesn't really expose a "link clicked" event, but it has a Navigating event that is close. This event fires whenever the browser is about to move to a new page. Fortunately, one of the data values passed to the event handler is the target URL. So all we have to do is build a link that contains the information we need to perform the database lookup.

I decided to store the relevant database lookup details as a collection (similar to the history stack), and create fake URL-like links that indicate which item in the collection to use. After a lot of thought and contemplation, I decided on the format of my fake URL links:


where x gets replaced by an index into the collection of links. It's simple, and it works. The collection of search details is a generic dictionary collection stored as a field within the form class.

Private Class SingleItemLink    Public LinkType As Library.LookupMethods    Public LinkID As Integer End Class Private ItemLinkSet As Collections.Generic.Dictionary( _    Of Integer, SingleItemLink) 

Then back in the HTML-building code, I add fake URLs and SingleItemLink objects in tandem. Here's some of the code used to add in author links, given a data reader with author name fields. (The entryID value supplies the x in library://x.)

Do While dbInfo.Read    ' ----- Add in this one author name.    holdText = FormatAuthorName(dbInfo)    entryID += 1    detailBody.AppendLine("<p><a href=""library://" & _       entryID & """>" & HTMLEncode(holdText & " [" & _       CStr(dbInfo!AuthorTypeName) & "]") & "</a></p>")    ' ----- Add in an author link.    newLink = New SingleItemLink    newLink.LinkType = General.LookupMethods.ByAuthorID    newLink.LinkID = CInt(dbInfo!ID)    ItemLinkSet.Add(entryID, newLink) Loop 

When the user clicks on a link in the embedded web browser, it triggers the Navigating event handler.

Private Sub SingleItemDetail_Navigating( _       ByVal sender As Object, ByVal e As System.Windows. _       Forms.WebBrowserNavigatingEventArgs) _       Handles SingleItemDetail.Navigating    ' ----- Follow the clicked link.    If (e.Url.Scheme = "library") Then _       FollowItemLink(CInt(e.Url.Host())) End Sub 

The e.Url.Scheme property returns the portion of the URL before the "://" characters, while e.Url.Host returns the first slash-delimited component just after these characters. That's where we stored the index into the ItemLinkSet dictionary. The FollowItemLink method extracts the lookup details from ItemLinkSet, and calls our trusty PerformLookup method, resulting in a new search that gets stored in the search history. Once again, generics come to our aid, letting us assign scanLink in this code block without explicit data type conversion.

Private Sub FollowItemLink(ByVal entryID As Integer)    ' ----- Given a character position in the single item    '       text panel, follow the link indicated by that item.    Dim scanLink As SingleItemLink    ' ----- Access the link.    scanLink = ItemLinkSet.Item(entryID)    If (scanLink Is Nothing) Then Return    ' ----- Perform a lookup as requested.    If (PerformLookup(scanLink.LinkType, _       CStr(scanLink.LinkID), False) = False) _       Then Return    ' ----- Store the history.    AddLookupHistory(scanLink.LinkType, CStr(scanLink.LinkID)) End Sub 

Enabling the Search Features

The ItemLookup form is ready to use. We just need to call it from the search fields on the main form. The PanelLibraryItem panel in MainForm.vb includes several ComboBox selection controls, but there is no code to fill them in. Let's add that code now. Access the source code for MainForm.vb, and locate the MainForm_Load event. There's already some code there that adjusts the form elements. Append the new list-filling code to the end of this routine.

Insert Snippet

Insert Chapter 16, Snippet Item 10.

Here's the portion of that new code that fills in the list of search methods.

' ----- Load in the list of search types. SearchType.Items.Add(New ListItemData( _    "Lookup By Title", LookupMethods.ByTitle)) SearchType.SelectedIndex = 0 SearchType.Items.Add(New ListItemData( _    "Lookup By Author", LookupMethods.ByAuthor)) SearchType.Items.Add(New ListItemData( _    "Lookup By Subject", LookupMethods.BySubject)) SearchType.Items.Add(New ListItemData( _    "Lookup By Keyword (Match Any)", _    LookupMethods.ByKeywordAny)) SearchType.Items.Add(New ListItemData( _    "Lookup By Keyword (Match All)", _    LookupMethods.ByKeywordAll)) SearchType.Items.Add(New ListItemData( _    "Lookup By Publisher", LookupMethods.ByPublisher)) SearchType.Items.Add(New ListItemData( _    "Lookup By Series Name", LookupMethods.BySeries)) SearchType.Items.Add(New ListItemData( _    "Lookup By Barcode", LookupMethods.ByBarcode)) 

The Clear button on the search panel resets all of the search fields and prepares them for a new search. Add a new ActSearchClear_Click event handler either by using the method selection fields just above the code editor window, or by double-clicking on the Clear button on the form itself. Then add the following code to the handler.

Insert Snippet

Insert Chapter 16, Snippet Item 11.

' ----- Clear the current search criteria. SearchType.SelectedIndex = SearchType.Items.IndexOf( _    CInt(LookupMethods.ByTitle)) SearchText.Text = "" SearchMediaType.SelectedIndex = _    SearchMediaType.Items.IndexOf(-1) SearchLocation.SelectedIndex = _    SearchLocation.Items.IndexOf(-1) 

Because the Library application will probably be used by many different patrons throughout the day, we should assume that a different person is using the program each time they return to the search panel. Let's simulate a click on the Clear button whenever the user returns to the search panel. Locate the existing TaskLibraryItem method, and add the following code to the end of the routine, just before the SearchText.Focus() statement.

Insert Snippet

Insert Chapter 16, Snippet Item 12.

ActSearchClear.PerformClick() If (ActSearchLimits.Top = LabelMoreLimitsTop.Top) Then _    ActSearchLimits.PerformClick() 

In the interest of being as user friendly as possible, let's add some "help text" to the search panel that varies based on the search type selected in the Search Type drop-down list. Add a new SearchType_SelectedIndexChanged event handler, and then add its code.

Insert Snippet

Insert Chapter 16, Snippet Item 13.

I won't list it all here because it's rather repetitive. The code simply examines the current selection in the SearchType control, and sets the LabelSearchHintsData label to some helpful descriptive text.

We're getting close. The only thing left to do is to perform the search when the user clicks the Lookup button. Add an event handler for ActSearch_Click, and then add its code.

Insert Snippet

Insert Chapter 16, Snippet Item 14.

Most of this routine checks for valid input before calling the ItemLookup form through its InitiateSearch public method.

Call (New ItemLookup).InitiateSearch( _    CType(searchMethod, Library.LookupMethods), _    Trim(SearchText.Text), mediaLimit, locationLimit) 

You've done it, doctor. You've added a heart to the patient. The program is ready to run and use for item lookups! If you've already added some named items, you can locate them using any of the relevant search methods. Try doing a title search, using just the "*" wildcard character for the search criteria.

Although the search feature works, you will find that some of the display elements on the ItemLookup form don't work perfectly. We never did get those columns working on the item results panel. Improvements are coming soon. With the next chapter's focus on GDI+, we'll soon be able to customize the display to our heart's content.

Start-to-Finish Visual Basic 2005. Learn Visual Basic 2005 as You Design and Develop a Complete Application
Start-to-Finish Visual Basic 2005: Learn Visual Basic 2005 as You Design and Develop a Complete Application
ISBN: 0321398009
EAN: 2147483647
Year: 2006
Pages: 247
Authors: Tim Patrick

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: