Advanced Visual Basic (Visual Studio 2008) - Project 14

Project 14 - Deriving Classes or Making an existing Class even more powerful

Launch Microsoft Visual Studio 2008.  Drop down the File menu and select New Project...

Be sure the Windows Application template in the New Project dialog is selected in the Templates pane on the right side, then type DerivedClassOverrides in the Name textbox (as shown below): 

Now click the OK button. 

Save the project by clicking on the Save All button on the toolbar.  This displays the Save Project dialog box, as shown:

Do not change the default Location path.  Be sure to uncheck the Create directory for solution option, as show above, before clicking on the Save button. 

This creates a new folder inside the My Documents\Visual Studio 2008\Projects\  folder named DerivedClassOverrides:

        My Documents\Visual Studio 2008\Projects\DerivedClassOverrides

Rename the Form file and change it's Name and Text properties

With the form file (Form1.vb) selected in the Solution Explorer window, so that it's File properties are displayed in the Properties window, change the File Name property to frmProject14.vb (don't forget to include the .vb extension).

Now click on the form in the Designer window to display it's properties:

Dynamically Creating all the Controls we will need

Let's begin by adding a Listbox control and two Labels controls to the form.  But instead of doing it the normal way—by pulling them from the control toolbox and dropping them on the form—let's create them with code.  In order to dynamically create a control with code at run-time requires you to do these steps, in this order, for each control you create:

  1. Dynamically construct each of the controls. If you want access to the control's event procedures at design-time, be sure to construct them with the WithEvents keyword in the Declaration's section, like this:
         'Construct ListBox1 with the WithEvents keyword
         '    so that we have easy access to it's event procedures
         '    at design-time.
        Public WithEvents ListBox1 As New ListBox
         'We won't be using the Event procedures of the
         '    Labels so we are constructing them normally .
        Public Label1 As New Label

  2.     Public
    Label2 As New Label
     
  3. Add the newly constructed controls to the Form's Control collection—normally in the Load event procedure of the form—like this:
         'Add our dynamically created controls to
         '    the form's controls collection.
        Me.Controls.Add(Label1)   
        Me.Controls.
    Add(Label2)
       
    Me.Controls.Add(ListBox1)

  4.  
  5. Configure each of the controls and make them visible, like this:
         'Configure Label1 and make it visible
        Label1.Text = "ListBox1"
       
    Label1.BackColor = Color.Aqua
       
    Label1.AutoSize = False
       
    Label1.TextAlign = _
                ContentAlignment.
    MiddleCenter
       
    Label1.Left = 5
       
    Label1.Top = 5
       
    Label1.Visible = True
         'Configure Label2 and make it visible
        Label2.Text = "ScrollListBox1"
       
    Label2.BackColor = Color.Coral

       
    Label2.AutoSize = False
       
    Label2.TextAlign = _
                ContentAlignment.
    MiddleCenter
        Label2.Left = 210
       
    Label2.Top = 5
       
    Label2.Visible = True
        
         'Configure ListBox1 and make it visible
        ListBox1.
    Left = 5
        ListBox1.
    Top = 30
        ListBox1.
    Width = 200
        ListBox1.
    Height = 150
        ListBox1.
    Visible = True
       
         'Fill ListBox1 with enough entries to
         '    generate a vertical scrollbar.
        Dim i As Integer
        For
    i = 0 To 50
           
    ListBox1.Items.Add("Entry Number #" & i)
        Next
    i

Add the first three lines of code in step 1 above, that construct the controls, to the Declaration's section.  Add the remaining code above to the Load event procedure of the form (frmProject14_Load).  Save and run your project.  When the program is running the form should look like this :

The goal in this project will be to synchronize the scrolling of two listboxes.  We will add another listbox called ScrollListBox1.  When ScrollListBox1 is scrolled up and down, ListBox1 should also scroll up and down and stay synchronized with it.  This should be an easy task to perform.  If we added a second listbox (and named it ScrollListbox1), we could set the TopIndex property of listbox1 equal to the TopIndex property of ScrollListbox1 in the Scroll event procedure of ScrollListbox1, with a single line of code, like this:

     ListBox1.TopIndex = ScrollListbox1.TopIndex   

Unfortunately, listboxes do not have a Scroll event procedure!  So we have no way of determining when ScrollListbox1 has been scrolled.

Making a more powerful ListBox control

We will now create a new listbox control and add a Scroll event procedure to it.  Begin by adding a new Class module to the project.  Right-click on the DerivedClassOverrides project in the Solution Explorer window (as shown below) and select Class... under the Add menu item on the context menu:

On the Add New Item dialog, be sure the Class template is selected in the Templates pane (see illustration below).  Type ScrollListBox.vb in the Name textbox and click the Add button:

This adds a new base class module to the project.  Your solution explorer window should look like this.  Notice how the icon looks in front of ScrollListBox.vb:

The new ScrollListBox class is a base class because it is not derived from any existing class.  To turn ScrollListBox into a class that is derived from the existing ListBox class, display the code view of the ScrollListBox and add the following Inherits statement directly below the Public Class ScrollListBox line at the top of the class module code window:

    Inherits System.Windows.Forms.ListBox

Your code view window for ScrollListBox should look like this:

After adding the above line of code and pressing enter, notice how the icon in front of ScrollListBox.vb—in the solution explorer windows—has changed.  This designates that ScrollListBox is now a derived class, not a base class:

Adding a new Event Procedure to a Derived class

When creating a new event procedure, you must always consider the e EventArgs parameter.  Remember, there are two parameters that are passed to an event procedure, like this—using the Load event procedure of a form as an example:

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

The sender parameter is the object that raised the event, and the e parameter holds the event arguments.  What the e parameter contains varies from one event procedure to the next.  The e parameter of a MouseMove event includes X and Y data members that represent the location of the mouse pointer.  The e parameter of a Click event, contains no values at all.  So e can contain several values or nothing depending upon which event it is.  When we add our own custom event procedures to a derived class, it is a good idea to follow the format of the pre-existing event procedures of intrinsic controls by including the sender and e parameters.  It is up to us to decide what event arguments are to be included in the e parameter.

So, along with creating each new event procedure comes the responsibility of creating a new derived EventArgs class for it's e parameter.

Creating a Derived EventArgs class to go with our new Scroll event procedure

For the purposes of this program—where synchronizing the scrolling of two listboxes is our main goal—the only value we really care about when the new Scroll event procedure of our ScrollListBox is raised is the value of the TopIndex property.  So that we can do this:

     ListBox1.TopIndex = ScrollListbox1.TopIndex   

We have direct access to this property, so we don't really need to pass it through a new derived EventArgs class.  But for the sake of this exercise we will pass it so you can see how it is done.  To create a derived EventArgs class named ScrollListBoxEventArgs, add the following code below the End Class line at the bottom of the code window of ScrollListBox.  This will create two separate classes within the same class module:

'Derived ScrollListBoxEventArgs class, for
'    passing custom values to the new Scroll
'    Event Procedure of the ScrollListBox control.
Public Class ScrollListBoxEventArgs
    Inherits System.EventArgs
     'Use a structure to combine several
     '    parameters values into a single
     '    variable, which will be the e.
    Public Structure EventArgValues
          'Besides Just TopIndex, let's also include
          '     the SelectedIndex value of the listbox
          '    just for the fun of it.
        Public TopIndex As Integer
        Public
SelectedIndex As Integer
    End Structure
    Public
Values As EventArgValues
     'We need to include a New procedure to
     '    initialize the data members in the Values
     '    (EventArgValues) variable when this
     '    object is created. 
    Public Sub New( _
           
ByVal Values As EventArgValues)
       
Me.Values = Values
    End Sub
End Class
 
'ScrollListBoxEventArgs

Using a New constructor to initialize an object

Whenever an instance of a class is created, Visual Studio attempts to execute a procedure named New, if it exists in that object.  When a programmer creates an instance of our ScrollListBoxEventArgs class they need a way to assign values to the TopIndex and SelectedIndex data members of the Values (EventArgValues structure) variable inside it.  So we are including a New procedure.  Our New procedure includes a Values As EventArgValues argument that must be provided by the programmer when constructing a ScrollListBoxEventArgs object.  Following is an example of the code required to construct an instance of a ScrollListBoxEventArgs object (do not type this code):

'Create an EventArgValues variable to assign
'     values to the TopIndex and SelectedIndex
'    data members during the construction of a
'    ScrollListBoxEventArgs object.
Dim eArg As _
    ScrollListBoxEventArgs.
EventArgValues
eArg.TopIndex = Number
eArg.SelectedIndex = Number
Dim e As ScrollListBoxEventArgs = _
           
New ScrollListBoxEventArgs(eArg)

It is common to use a structure (EventArgValues) in a derived EventArgs class so that you can combine multiple values (through the data members of the structure) to be passed through a single e parameter. 

Now that we have a derived EventArgs class (ScrollListBoxEventArgs) we can declare our new Scroll event procedure within the ScrollListBox derived class.  Add the following line of code below the Inherits line in the declarations section of the ScrollListBox code window:

    Public Event Scroll(ByVal sender As Object,  _
            ByVal e As
ScrollListBoxEventArgs)

We needed to create the ScrollListBoxEventArgs derived EventArgs class before we could declare the new Scroll event procedure.  As you can see in the code above, ScrollListBoxEventArgs was required for the e parameter part of the declaration.  Your code window of the ScrollListBox derived class should look like this:

As you see from the code above, creating a proper Scroll event procedure requires consideration of the e EventArgs parameter it must be passed.  In the case of our Scroll event, we are including with the TopIndex and—just for fun—SelectedIndex values.  But we don't have to stop there, and we are not restricted to just passing the values of properties.  We could also pass locally dimensioned variables whose values depend upon calculations or even reference other objects, the possibilities are endless.

Overriding the WndProc procedure to determine when a ListBox is scrolled

So far so good, but when do we raise the new Scroll event?  Since Listboxes don't have a Scroll event procedure normally, how do we determine when our derived ScrollListBox has been scrolled?

WndProc, the Mother of all Event Procedures

Within each object is the a Mother of all Event Procedures called WndProc.  The WndProc procedure is raised whenever anything happens to the object—from a mouse pointer passing over it, to keyboard presses when it has the focus.   It has just a single m parameter passed to it by the operating system that is declared like this (don't type this code):

    ByRef m As System.Windows.Forms.Message

The WndProc procedure is already being used by the ListBox class that our derived ScrollListBox class is inheriting.  In order to get access to the WndProc procedure from within a derived class we must declare an Overrides procedure for it, like this:

    Protected Overrides Sub WndProc( _
       
ByRef m As System.Windows.Forms.Message)

By overriding the WndProc procedure, we can intercept every event message that our ScrollListBox receives.  Including those event messages that the ListBox class—from which our ScrollListBox is derived—is ignoring.  Just because a Listbox doesn't have a Scroll event procedure doesn't mean it is not receiving scroll event messages through its WndProc procedure.  In fact it is, but it is ignoring them. 

Type the code above, to create an overrides WndProc procedure, into the code window of the ScrollListBox class and press enter.  An End Sub line should have been added automatically to complete the overrides WndProc procedure declaration.

Important!  Before we forget, it is essential when overriding the WndProc procedure to include a pass through call to the WndProc procedure of the class being derived.  Otherwise, we will intercept every event message before the original ListBox class receives it.  We would—by the single Overrides Sub WndProc declaration above—completely break the Listbox class.  Add the following line of code on the blank line above End Sub within the WndProc overrides procedure:

'It is VERY important after intercepting
'    any messages that we pass the message
'    on to the normal WndProc procedure of
'    the class being derived.
MyBase.WndProc(m)

The code for your overrides WndProc procedure should now look like this:

The m message argument contains a Msg data member that we can use to determine what type of event message occurred:

Every event message is represented by a different number value in Msg.  The number value of a Vertical Scroll event message is decimal 277.  Add the following code to the declaration section of the ScrollListBox class (below the Public Event Scroll line) to create a constant that represents the number value of a Vertical Scroll event message:

    Private Const WM_VSCROLL = &H115

We are using the hexadecimal number for decimal 277 (
&H115) in the code above, which follows the accepted convention for declaring event message constants.  The name we are using for the constant (WM_VSCROLL) also follows the naming convention of such constants.  WM stands for Windows Message, and VSCROLL stands for Vertical Scroll.  Now add the following code inside the WndProc procedure (above the MyBase.WndProc(m) line):

'Check for a vertical scroll event
If m.Msg = WM_VSCROLL Then

End If

The declarations and overrides WndProc procedure parts of the code in your ScrollListBox derived class should now look like this:

Determining what type of Scrolling occurred

The m message argument also contains a WParam data member that we can use to determine exactly what type of Vertical Scrolling happened.  We could add a Select Case statement inside the If-Then test above, that would look like this (don't type this code):

   'Examine the WParam to determine what
    '    kind of Vertical Scroll event occurred.
   
Select Case m.WParam.ToInt32
   
    Case WM_UPLINE
  'Hex 0
                 'Do something
        Case WM_UPPAGE  'Hex 2
                 'Do something
        Case WM_DOWNLINE  'Hex 1
                 'Do something
        Case WM_DOWNPAGE  'Hex 3
                 'Do something
        Case WM_STOPSCROLL  'Hex 8
                 'Do something
    End Select

We can test for scroll-up-one-line, scroll-up-one-page, scroll-down-one-line, and scroll-down-one-page event messages.  We can even determine when scrolling has stopped by testing for a Stop Scroll event message.  But we don't need to know how much the user scrolled the listbox, just that they did scroll it.  So in this particular situation we do not need to include the Select Case code above.   

We are now ready to raise our new scroll event procedure.  Add the following RaiseEvent code (italicized line) to your code:

    If m.Msg = WM_VSCROLL Then
        RaiseEvent Scroll(Me, e)
    End If

The e parameter will have a squiggly-underline because it is not declared yet.  Before we can raise our custom Scroll event procedure, we need to construct the e ScrollListBoxEventArgs parameter and assign appropriate values to its TopIndex and SelectedIndex data members.  Add the following line of code to the declarations section on the line below the WM_VSCROLL constant declaration:

     Private eArg As ScrollListBoxEventArgs.EventArgValues

We begin by dimensioning eArg, above, as an EventArgValues type—which, as you recall, is the structure in our derived ScrollListBoxEventArgs class that holds the individual event arguments (TopIndex and SelectedIndex).  Now add the following two lines of code above the RaiseEvent Scroll(Me, e) line inside the If-Then statement in the WndProc procedure:

    eArg.TopIndex = Me.TopIndex
   
eArg.SelectedIndex = Me.SelectedIndex
 
Now that its data members are initialized, we can pass eArg as the argument to the construction of our e ScrollListBoxEventArgs object.  Add the following line of code below the two lines above:

    Dim e As ScrollListBoxEventArgs = _
           
New ScrollListBoxEventArgs(eArg)

The If-Then code in your overrides WndProc procedure should now look like this:

If m.Msg = WM_VSCROLL Then
   
eArg.TopIndex = Me.TopIndex
    eArg.SelectedIndex = Me.SelectedIndex
    Dim
e As ScrollListBoxEventArgs = _
       
    New ScrollListBoxEventArgs(eArg)
   
RaiseEvent Scroll(Me, e)
End If

Testing the new ScrollListBox control

We are now ready to take the new ScrollListBox control for a test drive.  Save the project.  Open the code view of frmProject14.  Add the following line of code to the declarations section to construct a new ScrollListBox:

    Public WithEvents ScrollListBox1 _
            As
New ScrollListBox

Add the following code to the end of the frmProject14_Load event procedure above the For i = 0 To 50 line of code:

'Add the derived ScrollListBox control to the
'    form and configure it
Me.Controls.Add(ScrollListBox1)
ScrollListBox1.Left = 210
ScrollListBox1.Top = 30
ScrollListBox1.Width = 200
ScrollListBox1.Height = 150
ScrollListBox1.Visible = True

Add the following line of code inside the For i = 0 To 50 loop to fill ScrollListBox1 with the same initial values we are adding to ListBox1:

    ScrollListBox1.Items.Add("Entry Number #" & i)

The code at the bottom of the frmProject14_Load event procedure should now look like this:

Now drop down the Class list and select your new ScrollListBox1 item, then drop down the Methods list, and scroll down to find your new Scroll event procedure among all the normal ListBox event procedures that were inherited, and select it, as shown in the following illustration:

Inside the ScrollListBox1_Scroll event procedure add the following line of code to set the TopIndex property of ListBox1 equal to the TopIndex property of ScrollListBox1:

     ListBox1.TopIndex = e.Values.TopIndex   

Save the project and run it.  What happens when you vertically scroll ScrollListBox1?  Does Listbox1 stay synchronized with it?

Getting the Mouse Wheel scroll event to raise the Scroll event of ScrollListBox

While test running your program try scrolling ScrollListBox1 with the Mouse Wheel on the mouse (click inside ScrollListBox1 to give it the focus and scroll the Mouse Wheel up and down).  ScrollListBox1 should scroll up and down, but ListBox1 does not stay synchronized with it.  That is because there is a unique event message number for a Mouse Wheel scroll event which we are not intercepting, and so our Scroll event procedure is not being raised.

Go back to the code view of ScrollListBox.  We need to begin by declaring another event message constant for the Mouse Wheel scroll event.  Add this code to the declarations section of ScrollListBox, on the line below the declaration of the WM_VSCROLL constant:

    Private Const WM_MOUSEWHEEL = &H20A

Now add the italicized lines of code below (starting with the ElseIf line) to the overrides WndProc procedure to intercept the event message for a Mouse Wheel scroll event:

If m.Msg = WM_VSCROLL Then
   
eArg.TopIndex = Me.TopIndex
    eArg.SelectedIndex = Me.SelectedIndex
    Dim
e As ScrollListBoxEventArgs = _
       
    New ScrollListBoxEventArgs(eArg)
   
RaiseEvent Scroll(Me, e)
ElseIf  m.
Msg = WM_MOUSEWHEEL Then
     'Let the ListBox class process the m event
     '    message argument first, and then raise
     '    our Scroll event.
    MyBase.WndProc(m)
   
eArg.TopIndex = Me.TopIndex
    eArg.SelectedIndex = Me.SelectedIndex
    Dim
e As ScrollListBoxEventArgs = _
       
    New ScrollListBoxEventArgs(eArg)
   
RaiseEvent Scroll(Me, e)
     'Exit the Sub to prevent the m event message
     '   argument from being passed through to the
     '   Listbox class again. 
    Exit Sub
End If

The complete code in the overrides WndProc procedure should now look like this:

Letting the class being derived process the WndProc event message first

Sometimes we may need to allow the class being derived—a Listbox this case—to process the m event message first.  Then only after it is finished with it do we do our thing—raise our custom Scroll event in this case.  Notice that we have inserted a pass through call to MyBase.WndProc(m) as the first line of code in the newly added ElseIf code above.  We need to let the Listbox class process the Mouse Wheel scroll event message before we raise our scroll event.  If we intercept the m event message and raise our new Scroll event procedure before the Listbox class has a chance to process it, it may not scroll properly or may not even scroll at all.  Why doesn't it work that way when we are intercepting a Vertical Scroll event message?  Because when we use the vertical scroll bar of a listbox to scroll it up and down, the Vertical Scroll event message doesn't get passed to WndProc until after the scroll events for the scroll bars are processed by the Listbox class.

Also notice the Exit Sub line at the end of the new code we've added above.  This Exit Sub line will redirect execute out of the overrides WndProc procedure before m can be passed through to the Listbox class a second time. 

After adding the code above, save and test run your program.  Click in the ScrollListBox1 control to give it the focus and use the Mouse Wheel to scroll it.  Do the listboxes stay synchronized?


To copy a Project folder from your Projects folder on the Hard Drive to a floppy diskette or pen-drive follow these steps:

  1. Exit Visual Studio 2008 and insert the floppy diskette or pen-drive, that you want to copy the DerivedClassOverrides folder to:
  2. Select the My Documents item on the Start Menu to open the My Documents folder.
  3. In the My Documents folder, double-click the Visual Studio 2008 folder to open it.
  4. Double-click on your Projects folder to open it.
  5. Open the DerivedClassOverrides folder by double-clicking on it.  Inside the DerivedClassOverrides folder, delete the Obj and Bin folders—these folders are created automatically when you open a project.  You do not need to copy them, or their contents, to your floppy diskette or pen-drive.  Important: Be sure not to delete the My Project folder or Resources folder.
  6. Once you have deleted the Obj and Bin folders, hit the Backspace key once—or click the Back button on the toolbar.  This moves you from inside the DerivedClassOverrides folder to back inside your Projects folder.
  7. Right-click on the DerivedClassOverrides folder and selected: 31/2" Floppy A: or your pen-drive on the Send To fly-out menu.  This copies the DerivedClassOverrides folder to your floppy diskette or pen-drive.