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. Figure 4-6. Adding RolodexPanel to frmRolodexExample 4-12. Rolodex form Load event handlerPrivate 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 handlerPrivate 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 methodProtected 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 methodProtected 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
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 methodProtected 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 4.4.1. Building the Specialized FormsWith 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 methodProtected 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 4.4.2. Displaying the Rolodex, Step by StepThe 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:
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. 4.4.3. Clicking on an EntryWhen 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:
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. 4.4.4. Walking Through a Letter Button ClickTo see what happens when a Letter button is clicked, set break points in:
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. Figure 4-7. Running the completed Rolodex |