This chapter's project will add a lot of code to the Library Program, as much as 25 percent of the full code base. Most of it is identical to code we added in earlier chapters, so I won't print it all here. There's a lot to read here, too, so I won't overload you with pasting code snippets right and left. But as you add each new form to the project, be sure to look over its code to become familiar with its inner workings.

Project Access

Load the "Chapter 12 (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 12 (After) Code" instead.

Overloading a Conversion

Operator overloading is new with Visual Basic's 2005 release. When I originally designed the Library Project using Visual Basic 2003, operator overloading was unknown in the language, and I had to make due with the features I had available. Looking back, it's not easy to say, "This section of code would work 100 times better through operator overloading." There just isn't much code like that in a typical business application that shuttles around SQL statements and related data.

But enough of my whining. Operator overloading is a useful tool, and I have grown especially fond of the CType overload. Let's add a CType overload to a class we first designed back in Chapter 8, "Classes and Inheritance": ListItemData. This class exposes both ItemText and ItemData properties, providing access to the textual and numeric aspects of the class content. Its primary purpose is to support the tracking of ID numbers in ListBox and ComboBox controls. If we need to know the ID number of a selected item in a ListBox control (let's name it SomeList), we use code similar to the following.

Dim recordID As Integer = _
   CType(SomeList.SelectedItem, ListItemData).ItemData

There's nothing wrong with that code. But I thought, "Wouldn't it be nice to convert the ListItemData instance to an Integer using the CInt function, and not have to mess with member variables like ItemData?"

Dim recordID As Integer = _
   CInt(CType(SomeList.SelectedItem, ListItemData))

Hmm. The code's not that different. But, hey, why not? Let's do it. To support this conversion, we need to add a CType overload to the ListItemData class. Open that class's file, and add the following code as a member of the class.

Insert Snippet

Insert Chapter 12, Snippet Item 1.

Public Shared Widening Operator CType( _
      ByVal sourceItem As ListItemData) As Integer
   ' ----- To convert to integer, simply extract the
   '       integer element.
   Return sourceItem.ItemData
End Operator

That's pretty simple. This widening conversion from ListItemData to Integer just returns the Integer portion of the instance. There are only about four or five places in the current Library Project that directly access the ItemData member, and it's not that important to go back and change them. But we'll use this conversion overload frequently in the new code added in this chapter.

Global Support Features

We need to add a few more global variables and common global routines to support various features used through the application. Two new global variables will track settings stored in the database's SystemValue table. Add them as members to the General module (in General.vb).

Insert Snippet

Insert Chapter 12, Snippet Item 2.

Public DefaultItemLocation As Integer
Public SearchMatchLimit As Integer

The Library program identifies books and other items as stored in multiple locations, such as multiple branches or storage rooms. DefaultItemLocation indicates which one of these locations, from the CodeLocation table, is the default. The "DefaultLocation" entry of the SystemValue database table stores this value permanently.

When searching for books, authors, or other things that could result in thousands of matches, the SearchMatchLimit indicates the maximum number of matches returned by such searches. It's stored as the "SearchLimit" system value.

Because we're already in the General module, add three more helper functions.

Insert Snippet

Insert Chapter 12, Snippet Item 3.

  • ConfirmDefaultLocation. This routine verifies that a valid "DefaultLocation" entry exists in the SystemValue table. It returns True on success.

  • FormatAuthorName. Given a SqlDataReader built from records in the Author table, this function formats and returns a friendly author name in the format "Public, John Q, Jr. (19001999)." Here's the main formatting code for just the name part, which does a lot of checking for empty (Null) fields:

    authorName = CStr(dbInfo!LastName)
    If (IsDBNull(dbInfo!FirstName) = False) Then
       authorName &= ", " & CStr(dbInfo!FirstName)
       If (IsDBNull(dbInfo!MiddleName) = False) Then _
          authorName &= " " & CStr(dbInfo!MiddleName)
    End If
    If (IsDBNull(dbInfo!Suffix) = False) Then _
       authorName &= ", " & CStr(dbInfo!Suffix)

    Similar code for the birth and death years appears right after this code block.

  • GetCopyDisposition. This routine provides a short description for the current status of a specific library item copy. It analyzes the item's and patron's records, and returns one of the following status code strings: New Item Copy, Checked In, Checked Out, Overdue, Missing, or Reference.

Record Editors and Supporting Forms

Now things really start to hop. We'll add 23 new forms to the application in this chapter. Most of them implement basic code editors, similar to the UserName.vb and GroupName.vb files we built in Chapter 11, "Security." Other forms exist to provide additional support for these record editors. I won't reprint anything I've gone over before, but I'll point out some interesting new code on our way through each of these 23 forms.

If you're following along in the "Before" version of this chapter's project, you will need to enable each form as you encounter it. To do this, select the file in the Solution Explorer window, and change the file's Build Action property (in the Properties panel) from "None" to "Compile."

Search-Limiting Forms

The first four forms allow the librarian to limit the information overload that comes through using a database with thousands of books, publishers, and authors. You probably remember that the generic ListEditRecords form displays all existing records from a table of records by default. This works fine for the security groups stored in the GroupName table since you probably won't have even a dozen of those. But listing all books in even a small library can generate quite an imposing list. And depending on the speed of your workstation, it can take a while to load all book titles into the list.

The four "search-limiting" forms help reduce the number of records appearing in the list at once. When the librarian accesses the list of books and other library items, the ItemLimit form (see Figure) provides a quick search prompt that reduces the listed results.

The ItemLimit form acts like a bar-room bouncer for items

The form lets the user retrieve all records, or specific items based on item name (with wildcard support). Once the matches are loaded, the user can access this form again by clicking on the Lookup button on the ListEditRecords form for those types of code editors that support lookups (authors, items, patrons, and publishers).

We are ready to include these four search-limiting forms in the project.

  • AuthorLimit.vb. This form limits author records as loaded from the Author table.

  • ItemLimit.vb. This is the form we just talked about. It limits the display of library items from the NamedItem table.

  • PatronLimit.vb. Just in case patrons are flocking to your library, this form lets you limit the records loaded from the Patron table.

  • PublisherLimit.vb. This form limits records from the Publisher table.

Keyword and Subject Editors

While most record editors provide a full editing experience through the ListEditRecords form, some are subordinate to other editor forms. Keywords and subjects are a good example. Although they each have their own independent tables (Keyword and Subject), I chose to allow editing of them through the form that edits individual library items, the NamedItem form (added later). That form manages all interactions between the Keyword and Subject records and the NamedItem table, all through the intermediate many-to-many tables ItemKeyword and ItemSubject.

The KeywordAdd and SubjectAdd forms provide a simple text entry form for a single keyword or subject. Include each of these forms now into the project.

  • KeywordAdd.vb

  • SubjectAdd.vb

More Named Item Support Forms

As we'll see later, the NamedItem form is one of the most complex forms added to the Library Project so far. It manages everything about a generalized library item (like a book). Each item can have multiple copies, authors, keywords, subjects, and so on. It's simply too much editing power to include on a single form. We already added two of the subordinate forms: KeywordAdd and SubjectAdd. Let's add five additional support forms.

  • AuthorAddLocate.vb. This form presents a wizard-like interface that lets the user add a new or existing author record to an item. "Authors" in the Library program is a generic term that refers to authors, editors, illustrators, performers, etc. This form's three wizard steps (1) let the user indicate the type of author via the CodeAuthorType table; (2) perform a search for an existing author by name; and (3) select from a list of matching author names. If the desired author isn't yet in the database, the last step allows a new author to be added. Figure shows the first two of these steps.

    The first two of three author wizard steps

    Most of the logic is controlled through the Next button's event handler. The code in this routine varies based on the current wizard panel in view (as indicated by the ActivePanel class-level variable). Here's the code that runs when the user clicks Next after selecting the author type.

    ' ----- Make sure a name type is selected.
    If (CInt(CType(NameType.SelectedItem, _
          ListItemData)) = -1) Then
       MsgBox("Please select a name type from the list.", _
          MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, _
    End If
    ' ----- Move to the search panel.
    ActivePanel = PanelCriteria
    SecondPanel.Visible = True
    FirstPanel.Visible = False
    ActBack.Enabled = True

    Did you see the first logic line in that code? We used the CInt conversion function to get an ItemData value from a list item. This calls our overloaded CType operator in the ListItemData class.

  • PublisherAddLocate.vb. This form is just like the AuthorAddLocate form, but focuses on publishers. Its wizard only has two steps because publishers are not grouped by type. It locates or adds records in the Publisher table. When it's time to add a publisher to an item, the item editor form calls the public PublisherAddLocate.PromptUser function. This function returns the ID of the selected publisher record, or -1 to abort the adding of a publisher. A return value of -2 clears any previously selected publisher ID.

  • SeriesAddLocate.vb. This form is similar to the PublisherAddLocate form, but it prompts for records from the CodeSeries table.

  • ItemAuthorEdit.vb. Once an author has been added to an item, the only way to change it to a different author is to remove the incorrect author, and add the correct author separately through the AuthorAddLocate form. But if the user simply selected the wrong author type (like "Author" instead of "Illustrator"), it's kind of a burden to search for the author name again just to change the type. The ItemAuthorEdit form lets the user modify the type for an author already added to an item. It modifies the ItemAuthor.AuthorType database field.

  • ItemCopy.vb. A library will likely have multiple copies of a particular book, CD, or other item. In the Library program, this means that each NamedItem record can have more than one ItemCopy record attached to it. Each copy is edited through the ItemCopy form (see Figure).

    Details only a librarian could love

Although this code does not inherit from BaseCodeForm as other record editors do, it still has many of the features of those forms, including a SaveFormData routine that writes records to the database.

One interesting thing that this form does have is support for reading barcodes. Many barcode readers act as a "wedge," inserting the text of a scanned barcode into the keyboard input stream of the computer. Any program monitoring for barcodes simply has to monitor normal text input.

Barcode wedge scanners append a carriage return (the Enter key) to the end of the transmitted barcode. This lets a program detect the end of the barcode number. But in most of the Library program's forms, the Enter key triggers the OK button and closes the form. We don't want that to happen here. To prevent this, we'll add some code to this form that disables the auto-click on the OK button whenever the insertion point is in the Barcode text entry field.

Insert Snippet

Insert Chapter 12, Snippet Item 4.

Private Sub RecordBarcode_Enter( _
      ByVal sender As Object, ByVal e As System.EventArgs) _
      Handles RecordBarcode.Enter
   ' ----- Highlight the entire text.

   ' ----- Don't allow Enter to close the form.
   Me.AcceptButton = Nothing
End Sub

Private Sub RecordBarcode_Leave( _
      ByVal sender As Object, ByVal e As System.EventArgs) _
      Handles RecordBarcode.Leave
   ' ----- Allow Enter to close the form again.
   Me.AcceptButton = ActOK
End Sub

Private Sub RecordBarcode_KeyPress(ByVal sender As Object, _
      ByVal e As System.Windows.Forms.KeyPressEventArgs) _
      Handles RecordBarcode.KeyPress
   ' ----- Ignore the enter key.
   If (e.KeyChar = ChrW(Keys.Return)) Then e.Handled = True
End Sub

With this code, when the user presses the Enter key in the Barcode field manually, the form will not close. But it's a small price to pay for barcode support.

Inherited Code Editors

Twelve of the forms added in this chapter inherit directly from the BaseCodeForm class. Add them to the project as I review each one.

  • Author.vb. The Author form edits records in the Author database table. As a typical derived class of BaseCodeForm, it overrides many of the public elements of its base class. Two overrides that we haven't yet used in earlier chapters are the UsesSearch and SearchForRecord methods. These allow the user of the ListEditRecords form to limit the displayed authors through the prompting of the AuthorLimit form described earlier in this chapter. (The FillListWithRecords override also calls SearchForRecord to prompt the user for the initial list of authors to display.)

    In SearchForLimit, the call to AuthorLimit.PromptUser returns a comma-separated string in "Last, First" format.

    ' ----- Prompt the user for the limited author name.
    exceededMatches = False
    userLimit = (New AuthorLimit).PromptUser()
    If (userLimit = "") Then Return

    The user can include the "*" character as a wildcard in the first or last name parts. The asterisk has become a common character to use in all types of wildcard searches. Unfortunately, it is not supported in SQL Server SELECT statements. SQL Server uses the "%" character for a wildcard instead (as do many other SQL-compliant database platforms). As SearchForLimit extracts the first and last names, it ensures that the right wildcard character is used.

    ' ----- Use the limits to help prepare
    '       the search text.
    limitLast = Trim(GetSubStr(userLimit, ",", 1))
    limitFirst = Trim(GetSubStr(userLimit, ",", 2))
    If ((limitLast & limitFirst) = "") Then Return
    If (InStr(limitLast, "*") = 0) Then limitLast &= "*"
    If (InStr(limitFirst, "*") = 0) Then limitFirst &= "*"
    limitLast = Replace(limitLast, "*", "%")
    limitFirst = Replace(limitFirst, "*", "%")

    This code uses our custom GetSubStr routine already added to the General module. Once the name parts are extracted, the Visual Basic Replace function replaces all instances of "*" with "%." You'll find similar code in the other record editors that allow limits on the list of records, such as the Publisher form added later.

    While you have the source code open for this form, zoom up to the top. There, you'll find an interesting Imports statement.

    Imports MVB = Microsoft.VisualBasic

    Normally, Imports is followed immediately by a namespace. This variation includes the "MVB =" prefix, which defines a shortcut for the Microsoft.VisualBasic namespace for code in this file. With Visual Basic importing so many namespaces into an existing class that also defines a lot of public members, there are bound to be member name conflicts. In this case, the conflict is the Left form member. Because this source code for the Author form sees everything through the prism of that form, when you include the keyword Left in your logic, the code naturally assumes you mean the form's Left property, which sets the left position of the form. The problem is that Left is also a common string manipulation function that extracts the left-most characters from a larger string.

    smallerString = Left(largerString, 5)

    In a form, this code generates an error because it thinks Left means Me.Left. To use the string version of Left, you have to prefix it with its namespace.

    smallerString = Microsoft.VisualBasic.Left( _
       largerString, 5)

    The special Imports statement lets us substitute a shorter name for the rather long "Microsoft.VisualBasic" namespace.

    smallerString = MVB.Left(largerString, 5)

    You will find a few instances of such code in this and other forms that include the MVB prefix.

    The Author form has one more notable element. A Name Matches label appears near the bottom of the form, as shown in Figure.

    The bottom of the Author form showing "Name Matches"

    This field helps the user avoid adding the same author to the database twice. As changes are made to the Last Name and First Name fields, the Name Matches field gets refreshed with matching author names found in the Author table. The RefreshMatchingAuthors routine counts the number of matching authors through the following code:

    sqlText = "SELECT COUNT(*) AS TheCount " & _
       "FROM Author WHERE LastName LIKE " & _
    If (Trim(RecordFirstName.Text) <> "") Then
       sqlText &= " AND FirstName LIKE " & _
          DBText(MVB.Left(Trim( _
          RecordFirstName.Text), 1) & "%")
    End If
    matchCount = CInt(ExecuteSQLReturn(sqlText))

    This is similar to the lookup code in the SearchForLimit routine, but it only adds a wildcard to the first name before doing the search.

  • CodeAuthorType.vb. The CodeAuthorType form edits records in the related CodeAuthorType table. Who knew?

  • CodeCopyStatus.vb. This form edits records in the CodeCopyStatus database table.

  • CodeLocation.vb. As expected, this form edits records in the CodeLocation table. Once you've added at least one record to that table, you'll be able to set the default location for the database. I'll discuss this again a little later in this chapter.

  • CodeMediaType.vb. The CodeMediaType form, which edits records in the CodeMediaType table, includes a few more fields than the other "Code" table editors. Most of the fields accept numeric input. Although I do a final check for valid numeric data just before writing the record to the database, I try to prevent any non-numeric data from showing up in the first place by restricting the acceptable keystrokes. For instance, the RecordCheckoutDays text field's KeyPress event includes this code:

    ' ----- Only allow digits and backspaces.
    If (e.KeyChar = vbBack) Or _
       (IsNumeric(e.KeyChar)) Then Return
    e.Handled = True

    Setting the e.Handled property to True stops Visual Basic from doing anything else (pretty much) with the entered key. It's a quick and easy way to dispose of a user-entered keystroke.

  • CodePatronGroup.vb. This form edits records in the CodePatronGroup table.

  • CodeSeries.vb. This editor manages records in the CodeSeries table. Earlier I mentioned how series names and keywords are subordinate to named items. But it made sense to me to also provide direct management for series names, in case you wanted to build up a common list before adding individual library items. So this form performs double-duty: You can access it as a standard record editor through the ListEditRecords form, and it's also used for a specific named item through the not-yet-added NamedItem form.

    When editing item-specific series names, the user first gets to search for a series name by typing it. Because I don't want them to have to retype the series name again in this editor form, I wanted to pass the typed series name into the CodeSeries form, but none of the overridden public methods supported this. So we'll need to add a new method that will accept the typed name. The AddRecord member already overrides the base function of the same name.

    Public Overrides Function AddRecord() As Integer
       ' ----- Add a new record.
       ActiveID = -1
       If (Me.DialogResult = Windows.Forms. _
          DialogResult.OK) Then _
          Return ActiveID Else Return -1
    End Function

    Let's add an overload to this function that includes a string argument. The caller will pass the originally typed text to this argument. We'll assign it to the RecordFullName control's Text property so that it shows up automatically when the form opens.

    Insert Snippet

    Insert Chapter 12, Snippet Item 5.

    Public Overloads Function AddRecord( _
          ByVal seriesText As String) As Integer
       ' ----- Add a new record, but use a starting value
       '       previously entered by the user.
       ActiveID = -1
       RecordFullName.Text = seriesText
       If (Me.DialogResult = Windows.Forms.DialogResult.OK) _
       Then Return ActiveID Else Return -1
    End Function

    Yes, we could have used some name other than AddRecord for this function and avoided adding an overload. But it's nice to keep things consistent.

  • Holiday.vb. This form manages the records in the Holiday table. In a later chapter, we'll add a cache of holidays within the program for quick access.

  • Patron.vb. The Patron form provides editing services for records in the Patron table, and appears in Figure.

    Most of the Patron form (Messages tab details are hidden)

    This form includes a TabControl to help break up the amount of fields the user has to experience at once. If you have ever used the tab control included with Visual Basic 6.0, you'll quickly appreciate the .NET replacement. It manages all of the panel-switching logic automatically when the user selects a different tab. And each panel is a separate TabPage class instance. In your code, forcing the tab control to display a different tab is as easy as assigning the appropriate TabPage instance to the TabControl object's SelectedTab property, as with this code line from the ValidateFormData function.

    TabPatron.SelectedTab = TabGeneral

    Although this form looks quite complex, it's almost entirely made up of code we've seen in other forms. Beyond the standard overrides of BaseCodeForm members, this form includes barcode scanning support borrowed from the ItemCopy form, password logic stolen from the UserName form, and name-matching code similar to that used in the Author form.

    I included a Manage Patron's Items button on the form, but its logic won't be added until a later chapter. An extra public function, EditRecordLimited, becomes important at that time.

  • Publisher.vb. The Publisher form lets the user edit the records in the Publisher table. It's a pretty simple form with only two data entry fields. A Status field indicates how many NamedItem records link to this publisher. A small button appears to the right of the text entry field for the publisher's web site. This is the "show me the web site" button, and when clicked, brings up the supplied web page in the user's default browser. To enable this button, add the following code to the ShowWeb button's Click event handler.

    Insert Snippet

    Insert Chapter 12, Snippet Item 6.

    ' ----- Show the web site displayed in the field.
    Dim newProcess As ProcessStartInfo
    On Error Resume Next
    If (Trim(RecordWeb.Text) = "") Then Return
    newProcess = New ProcessStartInfo(Trim(RecordWeb.Text))
  • SystemValue.vb. This code editor handles items in the SystemValue table. While we will connect it to a link on the main Library form in this chapter, we will change this access method in a future chapter.

Well, that's 11 of the 12 derived forms. The last one is the NamedItem form, shown here in Figure.

The NamedItem form with the General tab active

The NamedItem form is the largest and most complex of the forms that derive from BaseCodeForm. It edits primary library items recorded in the NamedItem database table. It's complex because it also directly or indirectly manages records in other subordinate tables: ItemAuthor, ItemCopy, ItemKeyword, ItemSubject, and indirectly Author, Keyword, Publisher, and Subject.

All of the fields on the General and Classification tabs are basic data entry fields that flow directly into the NamedItem table, just as is done with the other record-editing forms. The Publisher and Series fields use separate selection forms (PublisherAddLocate and SeriesAddLocate) to obtain the ID values stored in NamedItem. Here's the code that looks up the publisher.

' ----- Prompt the user.
newPublisher = (New PublisherAddLocate).PromptUser()
If (newPublisher = -1) Then Return

' ----- Check to clear the publisher.
If (newPublisher = -2) Then
   RecordPublisher.Text = "Not Available"
   PublisherID = -1
End If

The other four tabsAuthors/Names, Subjects, Keywords, and Copiesmanage subordinate records. The code is pretty consistent between the four different tabs, so I'll limit my comments to the Authors/Names tab (see Figure).

The NamedItem form with the Authors/Names tab active

The controls on this tab are quite similar to those on the ListEditRecords form; they exist to manage a set of records in a table. In this case, it's the ItemAuthor table. For the presentation list, I chose to use a ListView control instead of a standard ListBox control. By setting a ListView controls's View property to "Details," setting its FullRowSelect field to True, and modifying its Columns collection (see Figure), you can quickly turn it into a multi-column list box.

The ColumnHeader editor for a ListView control

When you add an item to this list, you also have to add "sub-items" to have anything appear in all but the first column.

Dim newItem As Windows.Forms.ListViewItem = _
   AuthorsList.Items.Add("John Smith")

The Add button brings up the AuthorAddLocate form, while the Properties button displays the ItemAuthorEdit form instead.

Before any of the subordinate records can be added, the "parent" record must exist in the database. That's because the "child" records include the ID number of the parent record, and without a parent record, there is no parent ID number. If you look in each of the Add button routines on this form, you will find code like this.

' ----- The record must be saved first.
If (ActiveID = -1) Then
   ' ----- Confirm with the user.
   If (MsgBox("The item must be saved to the database " & _
      "before authors or names can be added. Would you " & _
      "like to save the record now?", _
      MsgBoxStyle.YesNo Or MsgBoxStyle.Question, _
      ProgramTitle) <> MsgBoxResult.Yes) Then Return

   '  ----- Verify and save the data.
   If (ValidateFormData() = False) Then Return
   If (SaveFormData() = False) Then Return
End If

If this is a brand-new NamedItem record (ActiveID = -1), this code will save it before allowing the user to add the subordinate record. Any invalid data that prevents the record from being saved will be caught in the call to ValidateFormData.

Actually, the calls to both ValidateFormData and SaveFormData are the same ones that occur when the user clicks on the OK button. Normally, that triggers a return of the new record's ID number to the calling form. But what if SaveFormData gets called by adding an author, but then the user clicks the Cancel button (which normally returns a -1 value to indicate "no record added")? To avoid that, the SaveFormData function sets a class-level variable named SessionSaved.

SessionSaved = True

This flag is cleared when the form first opens, but is set to True pretty much any time a subordinate record changes. The NamedItem form's overridden AddRecord and EditRecord functions check for this flag before returning to the calling form.

If (Me.DialogResult = Windows.Forms.DialogResult.OK) Or _
   (SessionSaved = True) Then Return ActiveID Else Return -1

There's lots of other interesting code in the NamedItem form. But at nearly 1,400 lines (not counting the related designer code), I'll have to let you investigate it on your own.

Connecting the Editors to the Main Form

OK, take a breath. That was a lot of code to go through. But if you run the program now, you won't see any difference at all. We still need to connect all of the record editors to the main form. They all connect through the LinkLabel controls on the main form's Administration panel (PanelAdmin). We need to add 12 LinkClicked event handlers to access all of the new and various forms. Go ahead and add them now to the MainForm class.

Insert Snippet

Insert Chapter 12, Snippet Item 7.

Each of the LinkClicked event handlers is almost a mirror image of the other, except for a few object instance names here and there. Here's the code that handles a click on the Publisher link label.

Private Sub AdminLinkPublishers_LinkClicked( _
      ByVal sender As System.Object, ByVal e As _
      System.Windows.Forms.LinkLabelLinkClickedEventArgs) _
      Handles AdminLinkPublishers.LinkClicked
   ' ----- Make sure the user is allowed to do this.
   If (SecurityProfile(LibrarySecurity.ManagePublishers) = _
         False) Then
      MsgBox(NotAuthorizedMessage, MsgBoxStyle.OkOnly Or _
         MsgBoxStyle.Exclamation, ProgramTitle)
   End If

   ' ----- Let the user edit the list of publishers.
   ListEditRecords.ManageRecords(New Library.Publisher)
   ListEditRecords = Nothing
End Sub

After doing a quick security check, the code calls up the standard ListEditRecords form, passing it an instance of the record editor it is to use.

There are still a few inactive links on the Administration panel that we'll enable in later chapters.

Settings the Default Location

The program is now ready to run with all of its new features in place. Because we added only administrative features, you must click the Login button in the upper-right corner of the main form before gaining access to the Administration panel and its features. Unless you changed it, your login user name is "admin" with no password.

Although you can now run the program and access all of the record editors, you won't be able to add new item copies until you set a default location. To set the location:

  1. Add at least one location through the Locations link on the Administration panel.

  2. Obtain the ID number of the CodeLocation record you want to be the default. You can use SQL Server Management Studio Express's query features to access the records in this table. If this is the first time you've added records to the CodeLocation table, the first item you add will have an ID value of 1.

  3. Back in the Library program, edit the SystemValue table through the System Values link on the Administration panel.

  4. Add or modify the "DefaultLocation" system value, setting its value to the ID number of the default location record.

Alternatively, you can update the "DefaultLocation" record in the SystemValue table directly using SQL Server Management Studio Express. If the ID of the location to use is 1, use this SQL statement to make the change.

UPDATE SystemValue
   SET ValueData = '1'
   WHERE ValueName = 'DefaultLocation'

In a future chapter, we'll add a more user-friendly method to update this default location.

Speaking of user friendly, we're about to enter the not-user-friendly but logic-friendly world of text structured data: XML.

 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows