Using the Custom Controls

Your custom control (RolodexPanel) will be housed within a form. The base form will be frmRolodex, whose job will be to provide common code for all the specialized forms (e.g., frmCustomerRolodex).

Back in the NorthWindWindows project, add a new form, frmRolodex. Set its size to 976,615. Open the Toolbox and expand the NorthWindControls Components section. Drag a RolodexPanel onto the new form, and drag a label named lblDisplay above it, as shown in Figure 4-6.

Everything in frmRolodex will be shared by all its derived types. You want to factor all the elements common to the derived forms into this form, so they will be as simple (and maintainable) as possible.

You need two members:

Protected orderedBy As String
Protected infoTable As Data.DataTable

The first, orderedBy, will keep track of the sort order for the data table. The second, infoTable, will hold a reference to a DataTable (e.g., the Customers table).

There are three event handlers you must create: one for when the form is loaded, the second for when the RowFillEvent is fired by the RolodexPanel, and the third for when the ButtonSelectedEvent is fired by the RolodexPanel.

When the form is loaded, you'll call LoadRolodex, a helper method, as shown in Example 4-12.

Adding RolodexPanel to frmRolodex

Figure 4-6. Adding RolodexPanel to frmRolodex

Example 4-12. Rolodex form Load event handler

Private Sub frmRolodex_Load( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
    LoadRolodex()
End Sub

This method will not be implemented in the base class, but will be implemented in the derived forms:

Protected Overridable Sub LoadRolodex()
End Sub
Protected Overridable Sub LoadRolodex(ByVal letter As Char)
End Sub

The second event handler responds to the RowFillEvent of the RolodexPanel, as shown in Example 4-13.

Example 4-13. RowFillEvent event handler

Private Sub OnFillRows( _
ByVal sender As Object, _
 ByVal e As EventArgs) _
 Handles RolodexPanel1.RowFillEvent
                  FillRows 
                      
(infoTable)
End Sub

This event handler calls the helper method FillRows, passing in the table to fill the rows from, as shown in Example 4-14.

Example 4-14. FillRows helper method

Protected Sub FillRows(ByVal infoTable As Data.DataTable)
   Dim column As Integer = 0
   Dim row As Integer = 0
   Me.RolodexPanel1.Clear()

   Dim loopcounter As Integer
   For loopcounter = 0 To Me.RolodexPanel1.NumEntriesPerPage -1
      Dim offset As Integer = Me.RolodexPanel1.Vbar.Value + _
      (row * 3) + column
      If offset >= infoTable.Rows.Count Then
         Exit For
      End If
      Dim dataRow As System.Data.DataRow = infoTable.Rows(offset)
      AddEntry(dataRow, column, row)
      column = column + 1
      If column = 3 Then
         column = 0
         row = row + 1
      End If
   Next
End Sub

The effect is to fill the Rolodex Panel with three rows of RolodexEntry objects.

The FillRows method is overloaded. The second version is called by the event handler that responds to an A-Z button being pressed.

Private Sub OnButtonSelected( _
ByVal sender As Object, _
ByVal e As EventArgs) _
Handles RolodexPanel1.ButtonSelectedEvent
    FillRows(Me.RolodexPanel1.CurrentLetter, Me.infoTable)
End Sub

This version of FillRows takes the letter to search for within the data (as well as the DataTable containing the data), as shown in Example 4-15.

Example 4-15. Overloaded version of FillRows helper method

Protected Sub FillRows( _
ByVal letter As Char, _
ByVal infoTable As Data.DataTable)
   Dim offset As Integer = 0
   Dim orderByName As Char = CType("A", Char)
   For Each dr As Data.DataRow In infoTable.Rows
      orderByName = dr(orderedby).ToString().ToUpper()(0)
      If orderByName >= letter Then
         Exit For
      End If
      offset = offset + 1
   Next
   Me.RolodexPanel1.Vbar.Value = offset

End Sub

Tip

For a description of how the If statement statement works in this code, please see the step-by-step description of clicking on a letter, later in this chapter.

Finally, the code that will be shared by the LoadRolodex override of all the derived forms is factored into the DoLoad method of the base class, shown in Example 4-16.

Example 4-16. DoLoad method DoLoad method

Protected Sub DoLoad( _
ByVal count As Integer, _
ByVal letter As Char, _
ByVal infoTable As Data.DataTable)
    Me.RolodexPanel1.Vbar.Maximum = count
    Me.lblDisplay.Text = count.ToString() + " records "
    Me.RolodexPanel1.Vbar.Value = 0
    FillRows(infoTable)
End Sub

Building the Specialized Forms

With the base form in place, you're ready to derive a specialized form: frmCusto-merRolodex.

Right-click on the NorthWindWindows project and choose Add → New Item and select Inherited Form. Name the new form frmCustomerRolodex.vb. You are then presented with the InheritancePicker. Select frmRolodex and press OK. A new form is created that inherits from frmRolodex named frmCustomerRolodex.

Notice that the panel and label are already in place (though the label may be invisible because we set its text to blank). You need access to the CustomersTableAdapter that you created earlier. Look in the toolbox and open the section marked NorthWindWindows Components. Drag the CustomersTableAdapter and the NorthwindDataSet to your form. Rename the dataset instance from NorthwindDataSet1 to NorthwindDataSet and CustomerTableAdapter1 to CustomerTableAdapter.

You want this form shown when the user clicks All Customers from the Welcome page. Go to btnAllClick in Welcome.vb and modify the btnAllClick method to invoke this method if the button's text is All Customers or if the menu contains the word Customers in the text, as shown in the bold code in Example 4-17.

Example 4-17. Modifying the AllClick event handler

Private Sub btnAllClick( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles btnAllCustomers.Click, btnAllSuppliers.Click, _
btnAllEmployees.Click, btnAllOrders.Click, mnuEmployeesShowAll.Click, _
mnuCustomersShowAll.Click, mnuOrdersShowAll.Click
Dim txt As String = String.Empty

If TypeOf sender Is Button Then
    txt = CType(sender, Button).Text
ElseIf TypeOf sender Is ToolStripMenuItem Then
    txt = CType(sender, ToolStripMenuItem).Name
End If
Dim oldCursor As Cursor = Me.Cursor
Me.Cursor = Cursors.WaitCursor
If txt.Contains("Customers") Then
    Dim rolodex As frmRolodex = New frmCustomerRolodex()
    rolodex.Show()
Else
    MessageBox.Show(txt + _
    " not yet implemented", "Not Yet Implemented", _
        MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End If
Me.Cursor = oldCursor
End Sub

Now you can go back to frmCustomerRolodex and override the three overridable methods from the base form. The first is LoadRolodex, which is overloaded. The code is shown in Example 4-18.

Example 4-18. Overriding the Rolodex form Load event handler

Protected Overrides Sub LoadRolodex()
   LoadRolodex(CChar("A"))
End Sub
Protected Overrides Sub LoadRolodex(ByVal letter As Char)
   CustomersTableAdapter.Fill( _
   CType(Me.NorthwindDataSet.Tables("Customers"), _
   NorthWindWindows.NorthwindDataSet.CustomersDataTable))

   Dim dataTable As NorthwindDataSet.CustomersDataTable = _
       CustomersTableAdapter.GetData()
   Dim count As Integer = dataTable.Rows.Count
   Me.infoTable = dataTable
   Me.orderedby = "CompanyName"
   DoLoad(count, letter, infoTable)
End Sub

In the second overload (the one that takes a letter), you call the Fill method on the CustomersTableAdapter, passing in the Customers table you extract from the NorthwindDataSet variable you just added to the form.

Your only other override is of AddEntry , shown in Example 4-19. This method is very specific to customers. It is also tightly coupled with the Customers table (it knows what values to extract) and with the RolodexCustomerEntry (it knows what values to set). It is, in many ways, the bridge between the RolodexCustomerEntry and its underlying table.

Example 4-19. Overriding the AddEntry method

Protected Overrides Sub AddEntry( _
ByVal dataRow As System.Data.DataRow, _
ByVal column As Integer, _
ByVal row As Integer)

   Dim entry As NorthWindControls.RolodexCustomerEntry = _
       New NorthWindControls.RolodexCustomerEntry()

   Dim companyName As String = String.Empty
   Dim contactName As String = String.Empty
   Dim phone As String = String.Empty
   Dim fax As String = String.Empty

   If IsDBNull(dataRow("CompanyName")) = False Then
      companyName = CStr(dataRow("CompanyName"))
   End If
   If IsDBNull(dataRow("ContactName")) = False Then
      contactName = CStr(dataRow("ContactName"))
   End If
   If IsDBNull(dataRow("Phone")) = False Then
      phone = CStr(dataRow("Phone"))
   End If
   If IsDBNull(dataRow("Fax")) = False Then
      fax = CStr(dataRow("Fax"))
   End If

   entry.LoadValues(companyName, contactName, phone, fax)
   entry.Left = Me.RolodexPanel1.StartX + _
       (column * Me.RolodexPanel1.XIncrement)
   entry.Top = Me.RolodexPanel1.StartY + _
       (row * Me.RolodexPanel1.YIncrement)
   AddHandler entry.EntrySelected, _
       AddressOf Me.RolodexPanel1.entry_click
   Me.RolodexPanel1.Add(entry)

End Sub

Displaying the Rolodex, Step by Step

The order of operations is critical here. The very best way to see this in action is to use your debugger and to set break points on the following methods:

  • Welcome.vb: btnAllClick

  • frmCustomerRolodex: all three methods

  • frmRolodex: frmRolodex_Load, FillRows (both overloads), and DoLoad

  • RolodexPanel: RolodexPanel_Load

  • RolodexCustomerEntry: Load_Values

When you ask to see all the customers by clicking on the All Customers button, the btnAllClick method is called in Welcome.vb. The button is examined and since its text is All Customers, the frmCustomerRolodex is created and shown.

When frmCustomerRolodex is loaded, the LoadRolodex method runs, fills the CustomersDataTable in the NorthWindDataSet, and then sets the member variable infoTable to the CustomersDataTable. The DoLoad method is then called in the base class, frmRolodex.

DoLoad sets the vertical scrollbar maximum and minimum values, sets lblDisplay.Text, then calls FillRows, passing in the CustomersDataTable. FillRows populates the three columns by extracting one row from the data table (Customers) and calling AddEntry.

AddEntry creates a new RolodexCustomerEntry object and sets its lblCompanyName, lblContactName, lblPhone, and lblFax based on the data in the DataRow.

It then sets the position (the column and row) of the entry and, most importantly, it adds an event handler for that entry. When the entry fires its EntrySelected event, you want the event to be handled by the entry_click method of the Rolodex Panel.

AddHandler entry.EntrySelected, _
    AddressOf Me.RolodexPanel1.entry_click

The entry is then added to the panel. This process repeats for as many entries as will fit in the form (defined as RolodexPanel.NumPageEntries). Once completed, FillRows is finished and the form is displayed.

Clicking on an Entry

When you click on an entry, it is lit up as red. The best way to see how this works is to put break points on:

  • RolodexCustomerEntry: InternalClick, SetSelectedProperties

  • RolodexEntry: InternalClick, Selected Set Accessor

  • RolodexPanel: entry_click

When the user clicks on an entry, that click is captured by RolodexCustomerEntry.InternalClick. It invokes MyBase.InternalClick, passing in a reference to itself. The base method raises the EntrySelected event, placing a reference to the RolodexEntry that was clicked into the sender argument.

RolodexPanel.entry_click handles that event and deselects every one of its controls. It then sets Selected to True on the one RolodexEntry that was passed in as sender. This invokes the Selected accessor on that Rolodex entry, which calls SetSelectedProperties.

SetSelectedProperties is overridden in RolodexCustomerEntry. When the item is not selected, its lblCompanyName background is set to Silver. When it is selected, the background is set to Red.

Walking Through a Letter Button Click

To see what happens when a Letter button is clicked, set break points in:

  • RolodexPanel: LetterButtonClick, LoadRolodex, and vBar_valueChanged

  • frmRolodex: OnButtonSelected, FillRows

Click on the letter T. The LetterButton_Click method is invoked. The result of this is to invoke LoadRolodex (passing in the letter), which sets the current letter, and then to raise the event ButtonSelectedEvent.

That event is caught by frmRolodex, which invokes the FillRows method, passing in the current letter and the data table. FillRows iterates through the rows until it finds a name that begins with a letter equal to or greater than the requested name, at which time it sets the vertical scrollbar value to the offset.

Setting the vertical scrollbar's value causes the vbar to raise the ValueChanged event, which you set in RolodexPanel1_Load to be handled by vbar_ValueChanged. That, in turn, raises the RowFillEvent, passing in the Rolodex Panel itself).

The RowFillEvent is handled by OnFillRows in frmRolodex, which calls the other FillRows method, passing in the DataTable. FillRows extracts the offset from the vertical scrollbar and creates the entries, as you saw earlier, filling in the panel, as shown in Figure 4-7.

Running the completed Rolodex

Figure 4-7. Running the completed Rolodex

Get Programming Visual Basic 2005 now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.