Chapter 4. Custom Controls

When the user clicks on the All Customers button of the Welcome page you’ve been building in previous chapters, a Rolodex of all the customers is displayed, as shown in Figure 4-1.

Complete Customer Rolodex
Figure 4-1. Complete Customer Rolodex

Unfortunately, Microsoft neglected to include a Rolodex control in Visual Studio 2005. No problem, though; in this chapter, you’ll implement your own as a custom control.

Tip

This code builds on the project started in the previous chapter. You can download the source code completed in Chapter 3 if you would like to start here.

Custom Controls

Custom controls come in three flavors:

A derived control

With a derived control, you take an existing control (e.g., a button) and give it new capabilities. For example, you might create a button that knows how many times it has been clicked.

A composite control

In a composite control, you take existing controls (whether provided by the Framework, or ones you’ve created) and you package them together into a single control.

A from-scratch control

Creating a custom control from scratch requires that you draw the control yourself using the GDI+ capabilities covered in the next chapter.

Design

There are two ways to approach a custom control of the complexity of the Rolodex. One is to build it incrementally; the other is to design it up front. I typically build incrementally, factoring out common code as I go. However, to present all the myriad iterations as functionality is added one step at a time would be a book in itself.

Thus, as an expedient, I’m going to build this as if I were omniscient, anticipating in advance a complete design that I can then implement.[*]

Your Rolodex will be housed in forms. You will have a Rolodex form for Customers, a second Rolodex form for Suppliers, and one each for Employees and Orders (you’ll only implement Customers here; the others will be left as an exercise).

These four forms will all derive from a common base form, frmRolodex. The job of frmRolodex will be to hold the code and design common to all the derived forms.

Within each of these forms will be a Rolodex panel. The job of the panel will be to:

  • Display all the buttons (a-z)

  • Display twelve Rolodex entries at a time

  • Display the scrollbars

The Rolodex panel is shown in Figure 4-2.

Rolodex panel
Figure 4-2. Rolodex panel

Within the panel are Rolodex entries. You’ll design a MustInherit base class, RolodexEntry, and then you’ll derive classes (like RolodexCustomerEntry) from it. These classes will specialize what information goes in the entry. For Customers, you want the customer name, contract, phone, and fax. For suppliers, Employees, and Orders, you’ll want different information.

In summary, for this chapter, you’ll build the following:

frmRolodex

The base class for all forms using a Rolodex

frmCustomerRolodex

Derives from frmRolodex, holds the customer Rolodex

RolodexPanel

Holds Rolodex entries, scrollbar, and A-Z buttons

RolodexEntry

The base class for all entries in the Rolodex panel

RolodexCustomerEntry

Derived from RolodexEntry, specialized for customers

To simplify project management, the forms will be kept in the NorthWindWindows project, but the custom controls (RolodexPanel, RolodexEntry, and RolodexCustomerEntry) will be in a new project named NorthWindControls. Both the NorthWindWindows project and the NorthWindControls project will be housed within the NorthWindWindows solution, thus making it easy for the projects to be kept together and managed as a single development effort.

Building the Controls

Add a new project to your existing solution. To do so, right-click on the solution and choose Add New project. Leave the Location the same as the location for your previous project, but name the new project NorthWindControls. Be sure to set the template to Windows Control Library, as shown in Figure 4-3.

Create the new Windows Control Library project
Figure 4-3. Create the new Windows Control Library project

Visual Studio 2005 creates a new project (NorthWindControls) and within that project adds a UserControl, which it names UserControl1. Begin by renaming its file to RolodexPanel.vb.

To use the Rolodex panel, however, you need Rolodex entries. Therefore, you need to create a couple of additional custom controls.

To start, right-click on the new project and choose Add New User Control and name it RolodexEntry.vb. This will serve as the base class for all the specialized RolodexEntry controls. You will create a derived RolodexEntry type for customers (RolodexCustomerEntry), on which the discussion in this chapter will focus. If you like, you can also create RolodexEntry types for Suppliers, Employees, and Orders.

RolodexEntry will have a Boolean member: chosen.

Public MustInherit Class RolodexEntry
    Protected chosen As Boolean

In addition, you will define an event for this user control. As you remember, all controls can publish events. The controls you’ve used so far have had such events as Click. You can create your own custom event for your new control just by declaring it with the Event keyword. In this case, you’ll want to define an EntrySelected event, as follows:

Public Event EntrySelected(ByVal sender As Object, ByVal e As EventArgs)

Tip

You read this line of code as follows: EntrySelected is a public event that will be handled by a method that takes two parameters: one of type Object and the other of type EventArgs.

Provide a public property for the chosen member variable:

Public Property Selected() As Boolean
   Get
      Return Me.chosen
   End Get
   Set(ByVal value As Boolean)
      Me.chosen = value
      SetSelectedProperties()
   End Set
End Property

Notice that the Set accessor not only sets the value of chosen but also calls the method SetSelectedProperties:

Protected Overridable Sub SetSelectedProperties()
End Sub

SetSelectedProperties has no implementation, but it is marked Overridable. This indicates that the derived class (e.g., RolodexCustomerEntry) will override this method to do some work when the Selected property is set.

Finally, add a method, InternalClick , that raises the EntrySelected event, as shown in Example 4-1.

Example 4-1. InternalClick method
Public Overridable Sub InternalClick( _
ByVal sender As Object, _
ByVal e As EventArgs)
    RaiseEvent EntrySelected(sender, e)
End Sub

This method looks suspiciously like an event handler, except that it does not have the keyword Handles to indicate what events it does handle. This will be explained in time, but keep an eye on this method!

Before proceeding, build the project to ensure that the RolodexEntry exists so you can derive from it. Next, create the derived RolodexCustomerEntry.

To do so, right-click on the NorthWindControls project and choose Add New Item. In the Add New Item dialog, choose Inherited User Control and name the new control RolodexCustomerEntry.vb. Clicking Add will bring up the Inheritance Picker so that you can select which control you are inheriting from, as shown in Figure 4-4.

Inheritance Picker dialog
Figure 4-4. Inheritance Picker dialog

Click OK and your third custom control (RolodexCustomerEntry) is created. You want each RolodexCustomerEntry to have a fixed size, large enough to accommodate the information for a customer, as shown in Figure 4-5.

RolodexCustomerEntry design
Figure 4-5. RolodexCustomerEntry design

Set the size of the control to 225,75. Open the Toolbox and add the seven labels shown: lblCompanyName, lblContactPrompt, lblContactName, lblPhonePrompt, lblPhone, lblFaxPrompt, and lblFax.

The top label lblCompanyName has a BackColor of Silver and a font of Microsoft Sans Serif, 12pt, style = Bold. Set its AutoSize property to False, and set its size to 225,21. This will cause it to fill the top of the control. Set its TextAlign to TopLeft.

For the other six labels (which you can drag on to the control and then select all at once), set their font to Sans Serif, 8.25 and their BackColor property to Control. Set the TextAlign property to MiddleLeft, and set AutoSize to False. Set the size for the three labels in the left column to 56,16, and for the three in the right column to 145,16.

The name, text, and location of each of the seven labels are shown in Table 4-1.

Table 4-1. Label name, text, and location

Name

Text

Location

lblCompanyName

Liberty Associates, Inc.

0,0

lblContactPrompt

Contact:

17,28

lblContactName

Jesse Liberty

80,28

lblPhonePrompt

Phone:

17,44

lblPhone

617-555-1212

80,44

lblFaxPrompt

Fax:

17,60

lblFax

617-555-2121

80,60

Now open the code for RolodexCustomerEntry. You should see that it is already marked as Inherits NorthWindControls.RolodexEntry and that there is a collapsed region Windows Form Designer generated code. You may expand and examine that region, but do not edit the code.

It is time to override the overridable methods from the base class. Start by overriding SetSelectedProperties , as shown in Example 4-2.

Example 4-2. Overriding the SetSelectedProperties method
Protected Overrides Sub SetSelectedProperties()
    If Me.Selected Then
        Me.lblCompanyName.BackColor = Color.Red
    Else
        Me.lblCompanyName.BackColor = Color.Silver
    End If
End Sub

Remember that when the Selected property is set, this method is called. For the selected RolodexCustomerEntry, it sets the background color of its lblCompanyName label to Red. For any RolodexCustomerEntrys that are not selected, it sets the background color of lblCompanyName to Silver.

You will also override the InternalClick method, setting it to handle a click on any of the labels on the RolodexCustomerEntry form (or on the form itself), as shown in Example 4-3.

Example 4-3. Overriding the InternalClick method
Public Overrides Sub InternalClick( _
ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles lblCompanyName.Click, lblFax.Click, _
lblPhone.Click, lblContactName.Click, _
lblFaxPrompt.Click, lblPhonePrompt.Click, _
lblContactPrompt.Click, MyBase.Click
    MyBase.InternalClick(Me, e)
End Sub

Notice that it calls the base class’s InternalClick method (which, you remember, looked a lot like an event handler), passing a reference to itself (the selected RolodexCustomerEntry) and the eventArgs object it receives.

This effectively channels all responses to clicking anywhere in the RolodexCustomerEntry to the base class’s InternalClick method. It, in turn, raises the EntrySelected event, broadcasting a reference to the specific entry that was clicked.

Finally, you will add a method (LoadValues ) to load the values for the lblCompanyName, lblContactName, lblPhone, and lblFax labels, as shown in Example 4-4.

Example 4-4. LoadValues method
Public Sub LoadValues( _
 ByVal companyName As String, _
 ByVal contactName As String, _
 ByVal phone As String, _
 ByVal fax As String)
     Me.lblCompanyName.Text = companyName
     Me.lblContactName.Text = contactName
     Me.lblPhone.Text = phone
     Me.lblFax.Text = fax
 End Sub

You’ll see how this method is invoked later.

Building the Rolodex Panel

Return to the RolodexPanel that was created when you first created this new project. You are ready to give this panel (which will host any type of RolodexEntry) some substance.

First, set its size to 875,510.

Second, add a panel (from the toolbox). Name that panel pnlRolodex and set its size to 872,440, and place it near the upper left of the Rolodex Panel (location 4,4). Set its BorderStyle to Fixed3D.

Next, add a second, smaller panel to hold the buttons. Name it pnlNavigationButtons, then set its size to 848,40 and its location to 14,451. Within the pnlNavigationButtons panel, add 26 buttons, each of the same size: 32,23, each with a white background and a single capital letter as its text. Name the buttons btnA, btnB, ..., btnZ.

Set the Click event handler for all 26 buttons to LetterButton_Click (which you’ll implement shortly).

Now open the code for the Rolodex Panel and add these constants:

Public Const StartX As Integer = 32
Public Const StartY As Integer = 24
Public Const BufferSpace As Integer = 20
Public Const ScrollBarWidth As Integer = 25
Public Const RowsPerPage As Integer = 4
Public Const ColsPerRow As Integer = 3
Public Const NumEntriesPerPage As Integer = RowsPerPage * ColsPerRow

Add two events to the panel:

Public Event RowFillEvent(ByVal sender As Object, ByVal e As EventArgs)
Public Event ButtonSelectedEvent(ByVal sender As Object, ByVal e As EventArgs)

Add the following protected members:

Protected chosenLtr As Char
Protected xIncr As Integer
Protected yIncr As Integer
Protected vsb As VScrollBar = New VScrollBar()
Protected entry As RolodexEntry = Nothing

Create ReadOnly properties for all of these, (except for vsb, for which you will create a Read/Write property.) For example:

ReadOnly Property ChosenLetter() As Char
   Get
      Return chosenLtr
   End Get
End Property

When the panel is first loaded, it is important to set the size of the panels and to add a vertical scrollbar (along with an event handler for when that scrollbar is clicked). The Load event handler for the Rolodex panel form is shown in Example 4-5.

Example 4-5. Rolodex Panel form Load event handler
Private Sub RolodexPanel_Load( _
   ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load
   Dim entry As RolodexCustomerEntry = New RolodexCustomerEntry()
   xIncr = entry.Width + Me.BufferSpace
   yIncr = entry.Height + Me.BufferSpace
   Me.pnlRolodex.Height = RowsPerPage * _yIncrement + StartY
   Me.pnlNavigationButtons.Top = Me.pnlRolodex.Bottom + BufferSpace
   Me.pnlRolodex.Width = Me.pnlRolodex.Width - ScrollBarWidth
   Me.pnlRolodex.AutoScroll = False
   vsb.SmallChange = ColsPerRow
   vsb.LargeChange = NumEntriesPerPage
   vsb.Parent = Me
   vsb.Location = New Point(pnlrolodex.Right, pnlrolodex.Top)
   vsb.Size = New Size(ScrollBarWidth, pnlrolodex.Height)
   vsb.Minimum = 0
   AddHandler vsb.ValueChanged, AddressOf vbar_ValueChanged
End Sub

Note that the handler for the ValueChanged event of the vertical scrollbar has been set to vbar_ValueChanged. Example 4-6 shows how you implement this method.

Example 4-6. Vertical scrollbar ValueChanged event handler method
Protected Sub vbar_ValueChanged( _
ByVal sender As Object, ByVal e As EventArgs)
    RaiseEvent RowFillEvent(Me, New EventArgs())
End Sub

Each time the scrollbar is clicked, the RowFillEvent is called (which will cause the rows to be refilled with the newly visible rows).

Example 4-7 shows the event hander called when any of the A-Z buttons are pressed.

Example 4-7. Letter button Click event handler
Private Sub LetterButton_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnZ.Click, btnY.Click, btnX.Click, btnW.Click, btnV.Click, btnU.Click, _
btnT.Click, btnS.Click, btnR.Click, btnQ.Click, btnP.Click, btnO.Click, btnN.Click, _
btnM.Click, btnL.Click, btnK.Click, btnJ.Click, btnI.Click, btnH.Click, btnG.Click, _
btnF.Click, btnE.Click, btnD.Click, btnC.Click, btnB.Click, btnA.Click
   Me.entry = Nothing
   Dim oldCursor As Cursor = Me.Cursor
   Me.Cursor = Cursors.WaitCursor
   Dim btn As Button = CType(sender, Button)
   If btn IsNot Nothing Then
   Dim letter as char = CChar(btn.Text.ToUpper())
      Me.LoadRolodex(letter)
   End If
   Me.Cursor = oldCursor
   RaiseEvent ButtonSelectedEvent(sender, e)

End Sub

This event handler sets the cursor to the wait cursor, casts the sender to a button, and then invokes LoadRolodex, passing in the letter. After doing that, it raises the ButtonSelectedEvent, passing in the sender.

The LoadRolodex method is overloaded. One version sets the Rolodex to start with the letter A. The other sets it to begin with the letter you pass it, as shown in Example 4-8.

Example 4-8. Two version of the overloaded Rolodex form Load event handler
Protected Sub LoadRolodex()
   LoadRolodex(CType("A", Char))
End Sub


Protected Sub LoadRolodex(ByVal letter As Char)
    Me._currentLetter = letter
End Sub

You need a method to add an entry to the panel and one to clear all the entries out of the panel. The former is accomplished by passing in the entry as a Control (it is a custom control) and adding it to the pnlRolodex’s Controls collection, as shown in Example 4-9.

Example 4-9. Add method Add method
Public Sub Add(ByVal c As Control)
    Me.pnlRolodex.Controls.Add(c)
End Sub

The latter method (to clear all the entries in the panel) is accomplished by calling the Clear method within the Controls collection of pnlRolodex, as shown in Example 4-10.

Example 4-10. Clear method Clear method
Public Sub Clear()
    Me.pnlRolodex.Controls.Clear()
End Sub

Finally, you need a method to handle what occurs when an entry is clicked. The entry that was clicked will be passed in as sender. You’ll cast it to type RolodexEntry, then iterate through each of the controls in the panel’s Controls collection, casting each of them to RolodexEntry and setting its Selected property to False. Finally, the selected control has its Selected property set to True (see Example 4-11).

Example 4-11. entry_click method
Public Sub entry_click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs)
    chosenEntry = CType(sender, RolodexEntry)
    For Each c As Control In Me.pnlRolodex.Controls
        Try
            Dim re As RolodexEntry = CType(c, RolodexEntry)
            re.Selected = False
        Catch ex As Exception
            Continue For
        End Try
    Next
    chosenEntry.Selected = True

End Sub

Note that as you iterate through the controls in the Controls collection it is possible that you’ll come across controls that are not RolodexEntry controls. If so, the attempt at the cast (using CType) will throw an exception. That is why you wrap the cast in a Try/Catch block. The action of the Catch block is to go to the next iteration of the For loop.

Tip

The astute reader will note that in the case shown, the Continue For statement is redundant; had you just done nothing in the Catch statement, you’d fall through to the Next statement restarting the loop.

The Continue For statement is added as a precaution. (practice safe programming!) If you modify this loop later and add new code after the Catch statement but before the Next statement, the Continue For ensures that the new code will not be executed if an exception was raised on the cast.

Further, the incredibly perceptive and meticulous reader will also notice that this event handler has no Handles keyword. This event handler must be linked to the EntrySelected event of the entry object. That will be done by the associated form, as shown later.

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


[*] The design and code for the applications in this book, especially the Rolodex custom controls , are based on work done by Liberty Associates, Inc. on behalf of and owned by Catalyst, Inc. (http://catalystwomen.org/), and are used with their generous permission.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset