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.
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:
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:
EventEntrySelected(ByVal sender As Object, ByVal e As EventArgs)
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
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
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,
, that raises the
EntrySelected event, as shown in Example 4-1.
Example 4-1. InternalClick method
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
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.
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.
Set the size of the control to
225,75. Open the Toolbox and add the seven labels shown:
The top label
lblCompanyName has a
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
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
False. Set the size for the three labels in the left column to
56,16, and for the three in the right column to
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
Liberty Associates, Inc.
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
, as shown in Example 4-2.
Example 4-2. Overriding the SetSelectedProperties method
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
You will also override the
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
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 (
) to load the values for the
lblFax labels, as shown in Example 4-4.
Example 4-4. LoadValues method
You'll see how this method is invoked later.
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
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
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
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.
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.
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.
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
Controls collection, as shown in Example 4-9.
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
method within the
Controls collection of
pnlRolodex, as shown in Example 4-10.
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
Catch block. The action of the
Catch block is to go to the next iteration of the
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.
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.