Chapter 27. Fine-Tuning Classes

In Lesson 26 you learned how to build constructors, special methods that execute when an object is created. In this lesson you learn about other special methods you can give a class. In particular, you learn how to overload and override class methods.

OVERLOADING METHODS

Lesson 26 mentioned that you can give a class any number of constructors as long as they have different parameter lists. For example, it's common to give a class a parameterless constructor that takes no parameters, and one or more other constructors that take parameters.

Making multiple methods with the same name but different parameter lists is called overloading. Visual Basic uses the parameter list to determine which version to use when you invoke the method.

For example, suppose you're building a course assignment application and you have built Student, Course, and Instructor classes. You could give the Student class two versions of the Enroll method, one that takes as a parameter the name of the class in which the student should be enrolled and a second that takes a Course object as a parameter.

You could give the Instructor class similar versions of the Teach method to make the instructor teach a class by name or Course object.

Finally, you could give the Course class different Report methods that:

  • Display a report in a dialog if there are no parameters

  • Append a report to the end of a file if the method receives a FileStream as a parameter

  • Save the report into a new file if the method receives a String (the filename) as a parameter

Making overloaded methods is so easy that there's little else to say. Simply write each version as you normally would write a method. Just remember to give each version of the method a different parameter list.

OVERRIDING METHODS

When one class inherits from another you can add new properties, methods, and events to the new class to give it new features that were not provided by the parent class.

Once in a while it's also useful to replace a method provided by the parent class with a new version. This is called overriding the parent's method.

Before you can override a method, you should mark the method in the parent class with the Overridable keyword so it allows itself to be overridden. Next, add the keyword Overrides to the derived class's version of the method to indicate that it overrides the parent class's version.

Note

Overridable methods are also sometimes called virtual methods, particularly in other programming languages such as C++ and C#.

For example, suppose the Person class defines the usual assortment of properties: FirstName, LastName, Street, City, and so on. Suppose it also provides the following GetAddress function that returns the Person's name and address formatted for printing on an envelope:

' Return the Person's address.
Public Overridable Function GetAddress() As String
    Return FirstName & " " & LastName & vbCrLf &
        Street & vbCrLf & City & "    " & State & "    " & Zip
End Function

Now suppose you derive the Employee class from Person. An Employee's address looks just like a Person's except it also includes MailStop.

The following code shows how the Employee class can override the GetAddress function to return an Employee-style address:

' Return the Employee's address.
Public Overrides Function GetAddress() As String
    Return MyBase.GetAddress() & vbCrLf & MailStop
End Function

Notice how the function calls the base class's version of GetAddress to reuse that version of the function and avoid duplicated work.

Note

IntelliSense can help you build overridden methods. For example, when you type "Public Overrides" and a space in the Employee class, IntelliSense lists the overridable methods that you might be trying to override. If you select one, IntelliSense fills in a default implementation for the new method. The following code shows the code IntelliSense generated for the GetAddress function:

Public Overrides Function GetAddress() As String
    Return MyBase.GetAddress()
End Function

The most miraculous thing about overriding a method is that the object uses the method even if you invoke it from the base class. For example, suppose you have a Person variable pointing to an Employee object. Remember that an Employee is a kind of Person, so a Person variable can refer to an Employee, as in the following code:

Dim bob As New Employee() With
{
    .FirstName = "Bob", .LastName = "Boastful",
    .Street = "22 Breach Blvd", .City = "Bugsville",
    .State = "CO", .Zip = "82789", .MailStop = "B-52"
}
Dim bobAsPerson As Person = bob

Now if the code calls bobAsPerson.GetAddress(), the result is the Employee version of GetAddress. The address includes Bob's MailStop even though a Person object doesn't have a MailStop.

Note

You can think of the Overridable keyword as making a slot in the base class for the method. When you override the method, the derived class fills this slot with a new version of the method. Now, even if you call the method from the base class, it uses whatever is in the slot.

Overriding a class's ToString function is particularly useful. All classes inherit a ToString method from System.Object, the ultimate ancestor of all other classes, but the default implementation of ToString isn't always useful. By default, for classes that you defined such as Person and Employee, ToString returns the class's name.

FIGURE 27-1

Figure 27.1. FIGURE 27-1

For example, in the ListPeople example program, which is shown in Figure 27-1 and available as part of this lesson's code download, the Employee class's ToString function returns ListPeople.Employee. Though this correctly reports the object's class, it might be nice if it returned something that contained information about the object's properties. In this example, it would be better if it returned the Employee object's first and last names.

The following code shows how you can override the ToString function to return an Employee's first and last name:

' Return first and last name.
Public Overrides Function ToString() As String
    Return FirstName & " " & LastName
End Function

This makes more sense. Now your program can use an Employee object's ToString method to learn about the object.

Overriding ToString also has a nice side benefit for Windows Forms development. Certain controls and parts of Visual Studio use an object's ToString method to decide what to display. For example, the ListBox and ComboBox controls display lists of items. If those items are not simple strings, the controls use the items' ToString methods to generate output.

If the list is full of Employee objects and you've overridden the Employee class's ToString method, then a ListBox or ComboBox can display the employees' names.

The ListPeople example program demonstrates method overriding. When it starts, the program uses the following code to fill its ListBox with two Student objects and two Employee objects. Both of these classes inherit from Person.

' Make some people.
Private Sub Form1_Load() Handles MyBase.Load
    lstPeople.Items.Add(New Student("Ann", "Archer",
        "101 Ash Ave", "Debugger", "NV", "72837"))
    lstPeople.Items.Add(New Student("Bob", "Best",
        "222 Beach Blvd", "Debugger", "NV", "72837"))
    lstPeople.Items.Add(New Employee("Cat", "Carter",
        "300 Cedar Ct", "Debugger", "NV", "72837", "MS-1"))
    lstPeople.Items.Add(New Employee("Dan", "Dental",
        "404 Date Dr", "Debugger", "NV", "72837", "MS-2"))
End Sub

The Employee class overrides its ToString method, so you can see the Employees' names in Figure 27-1 instead of their class names. The Student class does not override its ToString method, so Figure 27-1 shows class names for the Student objects.

If you double-click a person in the list, the program executes the following code:

' Display the selected Person's address.
Private Sub lstPeople_DoubleClick() Handles lstPeople.DoubleClick
    Dim personClicked As Person = DirectCast(lstPeople.SelectedItem, Person)
    MessageBox.Show(personClicked.GetAddress())
End Sub

This code converts the ListBox's selected item into a Person object. The item is actually either a Student or an Employee but both of those inherit from Person (they are kinds of Person), so the program can treat them as Persons.

The program calls the object's GetAddress function and displays the result. If the object is actually a Student, the result is a basic name and address. If the object is actually an Employee, the result is a name and address plus mailstop.

In addition to ListBoxes and ComboBoxes, some parts of Visual Studio use an object's ToString method, too. For example, if you stop an executing program and hover the mouse over an object in the debugger, a tooltip appears that displays the results of the object's ToString function.

TRY IT

In this Try It, you build a simple drawing application. You build a Shape class to represent a drawn shape and then derive the ShapeRectangle and ShapeEllipse classes from that one.

You give the Shape class an overridable Draw method that takes as a parameter a Graphics object and draws the shape on the object. The Shape class's version of Draw simply draws a rectangle with a big X in it.

You make the ShapeRectangle and ShapeEllipse classes override Draw to produce their own shapes.

Finally, you add overloaded versions of Draw that take pen and brush objects as parameters to use when drawing.

Note

You can download the code and resources for this Try It from the book's web page at www.wrox.com or www.vb-helper.com/24hourvb.html. You can find the programs in the Lesson27 folder.

Lesson Requirements

In this lesson:

  • Start a new program and create the Shape class.

    • Give the class a property named Bounds of type Rectangle. (Rectangle is a structure with properties X, Y, Width, and Height.)

    • Make a constructor that takes x, y, width, and height parameters and uses them to initialize the Bounds property.

    • Make an overridable Draw method that takes three parameters of type Graphics, Pen, and Brush. Make this method draw a box with an X in it on the Graphics object using the Pen and Brush.

    • Make a second Draw method that takes only a Graphics object as a parameter. Make it invoke the other Draw method, passing it the pen and brush Pens.Red and Brushes.Transparent.

  • Derive the ShapeRectangle class from the Shape class.

    • Make a constructor that takes x, y, width, and height parameters and invokes the base class's constructor.

    • Override the Shape class's version of the Draw method that takes four parameters so it draws a rectangle.

    • Override the second Draw method so it calls the first, passing it the pen and brush Pens.Black and Brushes.Transparent.

  • Repeat the previous steps for the ShapeEllipse class.

  • In the main form, create a List(Of Shape).

  • In the form's Load event handler, create some ShapeRectangles and ShapeEllipses and add them to the list.

  • Give the form a CheckBox labeled "Colored." In its CheckedChanged event handler, invalidate the form to force a redraw.

  • In the form's Paint event handler, redraw the form by looping through the Shapes in the list. If the CheckBox is checked, invoke the Shape objects' Draw methods, passing them the pen and brush Pens.Blue and Brushes.LightBlue. If the CheckBox is not checked, invoke the Draw methods with no parameters.

Hints

If gr is the Graphics object, then you can use these methods to draw:

  • gr.Clear(Me.BackColor) — Clears the object with the form's background color

  • gr.FillRectangle(brush, rect) — Fills a rectangle defined by rect with brush

  • gr.DrawRectangle(pen, rect) — Outlines a rectangle defined by rect with pen

  • gr.FillEllipse(brush, rect) — Fills an ellipse defined by rect with brush

  • gr.DrawEllipse(pen, rect) — Outlines an ellipse defined by rect with pen

In the Paint event handler, draw on the Graphics object provided by the event handler's e.Graphics parameter.

Step-by-Step

  • Start a new program and create the Shape class.

    • Give the class a property named Bounds of type Rectangle. (Rectangle is a structure with properties X, Y, Width, and Height.)

      1. Use code similar to the following:

        Public Property Bounds As Rectangle
    • Make a constructor that takes x, y, width, and height parameters and uses them to initialize the Bounds property.

      1. Use code similar to the following:

        Public Sub New(ByVal x As Integer, ByVal y As Integer,
         ByVal width As Integer, ByVal height As Integer)
            Bounds = New Rectangle(x, y, width, height)
        End Sub
    • Make an overridable Draw method that takes three parameters of type Graphics, Pen, and Brush. Make this method draw a box with an X in it on the Graphics object using the Pen and Brush.

      1. Use code similar to the following:

        ' Draw a box with an X in it using a pen and brush.
        Public Overridable Sub Draw(ByVal gr As Graphics,
         ByVal thePen As Pen, ByVal theBrush As Brush)
            gr.FillRectangle(theBrush, Bounds)
            gr.DrawRectangle(thePen, Bounds)
            gr.DrawLine(thePen, Bounds.Left, Bounds.Top,
                Bounds.Right, Bounds.Bottom)
            gr.DrawLine(thePen, Bounds.Right, Bounds.Top,
                Bounds.Left, Bounds.Bottom)
        End Sub
    • Make a second Draw method that takes only a Graphics object as a parameter. Make it invoke the other Draw method, passing it the pen and brush Pens.Red and Brushes.Transparent.

      1. Use code similar to the following:

        ' Draw a box with an X in it.
        Public Overridable Sub Draw(ByVal gr As Graphics)
            Draw(gr, Pens.Red, Brushes.Transparent)
        End Sub
  • Derive the ShapeRectangle class from the Shape class.

    • Make a constructor that takes x, y, width, and height parameters and invokes the base class's constructor.

      1. Use code similar to the following:

        Public Sub New(ByVal x As Integer, ByVal y As Integer,
         ByVal width As Integer, ByVal height As Integer)
            MyBase.New(x, y, width, height)
        End Sub
    • Override the Shape class's version of the Draw method that takes four parameters so it draws a rectangle.

      1. Use code similar to the following:

        Public Overrides Sub Draw(ByVal gr As Graphics,
         ByVal thePen As Pen, ByVal theBrush As Brush)
            gr.FillRectangle(theBrush, Bounds)
            gr.DrawRectangle(thePen, Bounds)
        End Sub
    • Override the second Draw method so it calls the first, passing it the pen and brush Pens.Black and Brushes.Transparent.

      1. Use code similar to the following:

        Public Overrides Sub Draw(ByVal gr As Graphics)
            Draw(gr, Pens.Black, Brushes.Transparent)
        End Sub
  • Repeat the previous steps for the ShapeEllipse class.

    1. The following code shows the ShapeEllipse class's code:

      Public Class ShapeEllipse
          Inherits Shape
      
          Public Sub New(ByVal x As Integer, ByVal y As Integer,
           ByVal width As Integer, ByVal height As Integer)
              MyBase.new(x, y, width, height)
          End Sub
      
          Public Overrides Sub Draw(ByVal gr As Graphics,
           ByVal thePen As Pen, ByVal theBrush As Brush)
              gr.FillEllipse(theBrush, Bounds)
              gr.DrawEllipse(thePen, Bounds)
          End Sub
      
          Public Overrides Sub Draw(ByVal gr As Graphics)
              Draw(gr, Pens.Black, Brushes.Transparent)
          End Sub
      End Class
  • In the main form, create a List(Of Shape).

    1. Use code similar to the following:

      ' Our list of shapes to draw.
      Private Shapes As New List(Of Shape)()
  • In the form's Load event handler, create some ShapeRectangles and ShapeEllipses and add them to the list.

    1. You can use code similar to the following, or you can create any ShapeEllipse and ShapeRectangle objects that you like:

      ' Make some shapes.
      Private Sub Form1_Load() Handles MyBase.Load
          Shapes.Add(New ShapeEllipse(50, 50, 200, 200))
          Shapes.Add(New ShapeEllipse(100, 80, 40, 60))
          Shapes.Add(New ShapeEllipse(100, 80, 40, 60))
          Shapes.Add(New ShapeEllipse(120, 100, 20, 30))
          Shapes.Add(New ShapeEllipse(160, 80, 40, 60))
          Shapes.Add(New ShapeEllipse(180, 100, 20, 30))
          Shapes.Add(New ShapeEllipse(135, 130, 30, 50))
          Shapes.Add(New ShapeRectangle(120, 190, 60, 5))
          Shapes.Add(New ShapeRectangle(75, 25, 150, 50))
      Shapes.Add(New ShapeRectangle(50, 75, 200, 10))
      End Sub
  • Give the form a CheckBox. In its CheckedChanged event handler, invalidate the form to force a redraw.

    1. The following code shows this event handler:

      ' Force a redraw.
      Private Sub chkColored_CheckedChanged() _
       Handles chkColored.CheckedChanged
          Me.Invalidate()
      End Sub
  • In the form's Paint event handler, redraw the form by looping through the Shapes in the list. If the CheckBox is checked, invoke the Shape objects' Draw methods, passing them the pen and brush Pens.Blue and Brushes.LightBlue. If the CheckBox is not checked, invoke the Draw methods with no parameters.

    1. Use code similar to the following:

      ' Redraw the objects.
      Private Sub Form1_Paint(ByVal sender As Object,
       ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
          e.Graphics.Clear(Me.BackColor)
      
          ' Loop through all of the shapes.
          For Each theShape As Shape In Shapes
              If (chkColored.Checked) Then
                  ' Draw with a pen and brush.
                  theShape.Draw(e.Graphics, Pens.Blue, Brushes.LightBlue)
              Else
                  ' Draw with no pen or brush.
                  theShape.Draw(e.Graphics)
              End If
          Next theShape
      End Sub

Figure 27-2 shows the result for the objects you created.

FIGURE 27-2

Figure 27.2. FIGURE 27-2

Note

Please select Lesson 27 on the DVD to view the video that accompanies this lesson.

EXERCISES

  1. Copy the complex number program you built in Lesson 26, Exercise 2 (or download Lesson 26's version from the book's web site). Override the class's ToString method so it returns the number in a form similar to "2 + 3i." Overload the ComplexNumber class's constructor and AddTo, MultiplyBy, and SubtractFrom functions so you can pass them a single Double parameter representing a real number with no imaginary part. Modify the form so you can test the new methods.

  2. Copy the bank account program you built in Lesson 26, Exercise 4 (or download Lesson 26's version from the book's web site). Derive a new OverdraftAccount class from the Account class. Give it a constructor that simply invokes the base class's constructor. Override the Debit method to allow the account to have a negative balance and charge a $50 fee if any debit leaves the account with a negative balance. Change the main program so the Account variable is still declared to be of type Account but initialize it as an OverdraftAccount. Hints: Don't forget to make the BankAccount class's version of Debit overridable; and to allow OverdraftAccount code to access the _Balance backing field, change that field from Private to Protected.

  3. Copy Lesson 24's Turtle program. The Turtle class has a Move method that moves the turtle a specified distance in the object's current direction. Overload this method by making a second version that takes as parameters the x and y coordinates where the turtle should move. Be sure to raise the OutOfBounds event if the point is not on the canvas. Hint: Can you reuse code somehow between the two Move methods?

Note

You can find solutions to this lesson's exercises in the Lesson27 folder inside the download available on the book's web site at www.wrox.com or www.vb-helper.com/24hourvb.html.

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

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