Chapter 8. Classes and Inheritance

How many .NET programmers does it take to change a light bulb? None—they call a method on the light bulb object, and it changes itself. Ha, ha, ha! That’s funny, but only if you understand the object-oriented programming (OOP) concepts that are the basic foundation of the .NET system. (Actually, it’s not even that funny if you do understand OOP.) Without OOP, it would be difficult to support core features of .NET, such as the central System.Object object, which is the basic foundation of the .NET system. Also, productivity would go way down among Windows developers, who are the basic foundation of the .NET system.

Although I briefly mentioned OOP development concepts in Chapter 1 and Chapter 2, it was only to provide some context for other topics of discussion. But in this chapter, I hold back no longer. After a vigorous discussion of general OOP concepts, I’ll discuss how you can use these concepts in your .NET code.

Object-Oriented Programming Concepts

If you’ve read this far into the book, it’s probably OK to let you in on the secret of object-oriented computing. The secret is: it’s all a sham, a hoax, a coverup. That’s right, your computer does not really perform any processing with objects, no matter what their orientation. The CPU in your computer processes data and logic statements the old-fashioned way: one step at a time, moving through specific areas in memory as directed by the logic, manipulating individual values and bits according to those same logic statements. It doesn’t see data as collective objects; it sees only bits and bytes.

One moment, I’ve just been handed this important news bulletin. It reads, “Don’t be such a geek, Tim. It’s not the computer doing the object-oriented stuff, it’s the programmer.” Oh, sorry about that. But what I said before still stands: the final code as executed by your CPU isn’t any more object-oriented than old MS-DOS code. But object-oriented language compilers provide the illusion that OOP is built into the computer. You design your code and data in the form of objects, and the compiler takes it from there. It reorganizes your code and data, adds some extra code to do the simulated-OOP magic, and bundles it all up in an EXE file. You could write any OOP program using ordinary procedural languages, or even assembly language. But applications that focus on data can often be written much more efficiently using OOP development practices.

The Object

The core of object-oriented programming is, of course, the object. An object is a person, place, or thing. Wait a minute, that’s a noun. An object is like a noun. Objects are computer data-and-logic constructs that symbolize real-world entities, such as people, places, or things. You can have objects that represent people, employees, dogs, sea otters, houses, file cabinets, computers, strands of DNA, galaxies, pictures, word processing documents, calculators, office supplies, books, soap opera characters, space invaders, pizza slices, majestic self-amortizing canals, plantations of ripening tea, a few of my favorite things, and sand.

Objects provide a convenient software means to describe and manage the data associated with one of these real-world objects. For instance, if you had a set of objects representing DVDs in your home video collection, the object could manage features of the DVD, such as its title, the actors performing in the content, the length of the video in minutes, whether the DVD was damaged or scratched, its cost, and so on. If you connected your application to the DVD-ROM player in your system, your object could even include a “play” feature that (assuming the DVD was in the drive) would begin to play the movie, possibly from a timed starting position or DVD “chapter.”

Objects work well because of their ability to simulate the features of real-world counterparts through software development means. They do this through the four key attributes of objects: abstraction, encapsulation, inheritance, and polymorphism.

Throughout this chapter, the term object usually refers to an instance of something, a specific in-memory use of the defined element, an instance with its own set of data, not just its definition or design. Class refers to the design and source code of the object, comprising the implementation.

Abstraction

An abstraction indicates an object’s limited view of a real-world object. Like an abstract painting, an abstracted object shows just the basic essentials of the real-world equivalent (see Figure 8-1).

Actually, the one on the left is kind of abstract, too

Figure 8-1. Actually, the one on the left is kind of abstract, too

Objects can’t perfectly represent real-world counterparts. Instead, they implement data storage and processes on just those elements of the real-world counterpart that are important for the application. Software isn’t the only thing that requires abstraction. Your medical chart at your doctor’s office is an abstraction of your total physical health. When you buy a new house, the house inspector’s report is an abstraction of the actual condition of the building. Even the thermometer in your back yard is an abstraction; it cannot accurately communicate all of the minor temperature variations that exist just around the flask of mercury. Instead, it gathers all the information it can, and communicates a single numeric result.

All of these abstract tools record, act on, or communicate just the essential information they were designed to manage. A software object, in a similar way, only stores, acts on, or communicates essential information about its real-world counterpart. For instance, if you were designing an object that monitored the condition of a building, you might record the following:

  • Building location and address

  • Primary construction material (wood, concrete, steel-beam, etc.)

  • Age (in years)

  • General condition (from a list of choices)

  • Inspector notes

Although a building would also have color, a number of doors and windows, and a height, these elements may not be important for the application, and therefore would not be part of the abstraction. Those values that are contained within the object are called properties. Any processing rules or calculations contained within the object that act on the properties (or other supplied internal or external data) are known as methods. Taken together, methods and properties make up the members of the object.

Encapsulation

The great advantage of software is that a user can perform a lot of complex and time-consuming work quickly and easily. Actually, the software takes care of the speed and the complexity on behalf of the user, and in many cases, the user doesn’t even care how the work is being done. “Those computers are just so baffling; I don’t know and I don’t care how they work as long as they give me the results I need” is a common statement heard in management meetings. And it’s a realistic statement too, since the computer has encapsulated the necessary data and processing logic to accomplish the desired tasks.

Encapsulation carries with it the idea of interfaces. Although a computer may contain a lot of useful logic and data, if there was no way to interact with that logic or data, the computer would basically be a useless lump of plastic and silicon. Interfaces provide the means to interact with the internals of an object. An interface provides highly controlled entries and exits into the data and processing routines contained within the object. As a consumer of the object, it’s really irrelevant how the object does its work internally, as long as it produces the results you expect through its publicly exposed interfaces.

Using the computer as an example, the various interfaces include (among other things) the keyboard, display, mouse, power connector, USB and 1394 ports, speakers, microphone jack, and power button. Often, the things I connect to these interfaces are also black boxes, encapsulations with well-defined public interfaces. A printer is a mystery to me. How the printer driver can send commands down the USB cable and eventually squirt ink onto 24-pound paper is just inexplicable, but I don’t know and I don’t care how it really works, as long as it does work.

Inheritance

Inheritance in .NET isn’t like inheritance in real life; no one has to die before it works. But as in real life, inheritance defines a relationship between two different objects. Specifically, it defines how one object is descended from another.

The original class in the object relationship is called the base class. It includes various and sundry interface members, as well as internal implementation details. A derived class is defined using the base class as the starting point. Derived classes inherit the features of the base class. By default, any publicly exposed members of the base class automatically become publicly exposed members of the derived class, including the implementation. A derived class may choose to override one, some, or all of these members, providing its own distinct or supplementary implementation details.

Derived classes often provide additional details specific to a subset of the base class. For instance, a base class that defines animals would include interfaces for the common name, Latin species name, number of legs, and other typical properties belonging to all animals. Derived classes would then enhance the features of the base class, but only for a subset of animals. A mammal class might define gestation time for birthing young, whereas a parallel avian-derived class could define the diameter of an egg. Both mammal and avian would still retain the name, species name, and leg count properties from the base animal class. An instance of avian would be an animal; an instance of mammal would be an animal. However, an instance of avian would not be a mammal. Also, a generic instance of animal could be considered as an avian only if it was originally defined as an avian.

Even though a base and derived class have a relationship, implementation details that are private to the base class are not made available to the derived class. The derived class doesn’t even know that those private members exist. A base class may include protected members that, although hidden from users of the class, are visible to the derived class. Any member defined as public in the base class is available to the derived class, and also to all users of the base class. (Visual Basic defines another level named friend. Members marked as friend are available to all code in the same assembly, but not to code outside the assembly. Public members can be used by code outside the defining assembly.)

Examples of inheritance do exist in the real world. A clock is a base object from which an alarm clock derives. The alarm clock exposes the public interfaces of a clock, and adds its own implementation-specific properties and methods. Other examples include a knife and its derived Swiss Army knife, a chair and its derived recliner, and a table and its derived Periodic Table of the Chemical Elements.

Polymorphism

The concepts introduced so far could be implemented using standard procedural programming languages. Although you can’t do true inheritance in a non-OOP language such as C, you can simulate it using flag fields: if a flag field named “type” in a non-OOP class-like structure was set to “mammal,” you could enable use of certain mammal-specific fields. There are other ways to simulate these features, and it wouldn’t be too difficult.

Polymorphism is a different avian altogether. Polymorphism means “many forms.” Because a derived class can have its own (overridden) version of a base class’s member, if you treat a mammal object like a generic animal, there could be some confusion as to which version of the members should be used, the animal version or the mammal version. Polymorphism takes care of figuring all this out, on an ad hoc basis, while your program is running. Polymorphism makes it possible for any code in your program to treat a derived instance as though it were its base instance. This makes for great coding. If you have a routine that deals with animal objects, you can pass it objects of type animal, mammal, or avian, and it will still work. This type of polymorphism is known as subtyping polymorphism, but who cares what its name is.

Another variation of polymorphism is overloading. Overloading allows a single class method (forget about derived classes for now) to have multiple forms, but still be considered as a single method. For instance, if you had a house object with a paint method (that would change the color of the house), you could have one paint method that accepted a single color (paint the house all one color) and another paint method that accepted two colors (the main color plus a trim color). When these methods are overloaded in a single class, the compiler determines which version to call based on the data you include in the call to the method.

Interfaces and Implementation

OOP development differentiates between the public definition of a class, the code written to implement that class, and the resultant in-memory use of that class as an object. It’s similar to how, at a restaurant, you differentiate between a menu, the cooking of your selection, and the actual food that appears at your table:

  • The description of an item on the menu is (to some extent) its interface; it describes what the real object will expose publicly in terms of taste, smell, and so on.

  • The method used by the kitchen staff to make the food is the implementation; it’s how the meal is prepared. There may be different implementations by different restaurants for the same menu item. In objects, the implementation is hidden from public view; in a restaurant, food preparation is thankfully hidden from view or no one would ever eat there.

  • The food you receive from the kitchen is—ta-da!—the object, the actual instance of what the menu described. Many hungry customers may each order the same menu item, and each would receive a distinct instance of the food.

OOP in Visual Basic and .NET

Conceptually, OOP really isn’t that complex. Since both humans and programmers interact with real-world objects and instances every day, it’s pretty easy to wrap their minds around the idea of programming with objects. But how easy is it to communicate these object concepts to the computer through the Visual Basic compiler and the .NET Framework? Can it be done without weekly sessions on a shrink’s comfy sofa? Duh! It’s Visual Basic; of course it’s easy.

One reason objects are so easy in .NET is that they have to be. Everything in your .NET program is part of an object, and if everything about .NET was hard, you’d be reading a book on Macintosh development right about now. But it’s not too hard because the Visual Basic implementation of objects parallels the conceptual ideas of objects.

Classes

Visual Basic uses classes and structures to define objects. I’ll talk about structures a little later in the chapter. The Class keyword starts the definition of a class.

Class Superhero
   ' ----- Class-related code goes here.
End Class

That’s most of it: the Class keyword, and a name for the class (“Superhero,” in this case). All classes reside in a namespace (discussed way back in Chapter 1). By default, your classes appear in a namespace that is named after your project. You can alter this in the project properties (to set the top-level namespace for your assembly) and with the Namespace statement (to indicate relative namespaces from your assembly’s top-level namespace).

Namespace GoodGuys
   Class Superhero
   End Class
End Namespace

If your application’s default namespace is WindowsApplication1, the class in this sample code would be identified as WindowsApplication1.GoodGuys.Superhero. You can add any number of classes to a namespace. Classes that use the same name, but that appear in different namespaces, are unrelated.

The members of a class appear between the Class and End Class clauses. You can also split a class’s definition into multiple source code files. If you do split up a class like this, at least one of the parts must include the keyword Partial in the definition.

Partial Class Superhero

As with variable definitions, classes are defined using one of the access modifier keywords: Public, Private, Protected, Friend, or Protected Friend. Flip back to Chapter 6, in the "Variables" section, if you need a refresher course.

The .NET Framework Class Libraries (FCLs) are simply loaded with classes and objects, and they are all pretty much defined with this simple keyword: Class.

Class Members

Calling your class Superhero won’t endow it with any special powers if you don’t add any members to the class. All class members must appear between the Class and End Class boundaries, although if you use the Partial feature to break up your class, you can sprinkle the members among the different class parts in any way you wish.

You can include 11 different kinds of members in your Visual Basic classes. Other books or documents may give you a different number, but they’re wrong, at least if they organize things the way I do here:

Variable fields

Value type and reference type variables can be added directly to your class as top-level members. As full class members, they are accessible by any code defined in the same class, and possibly by code that uses your class. Variables are defined using one of the access modifiers.

Class Superhero
   Public Name As String
   Protected TrueIdentity As String
End Class

Variable fields are quick and convenient to add to classes, but sometimes they are a little too freewheeling. Public fields can be modified at will, without any limitations, even if you desire to limit the allowed range of a field. Also, fields don’t work directly with all Visual Basic features, including some LINQ-specific features. When problems such as these arise, you can use property members instead of variable field members. I’ll introduce properties in just a few paragraphs.

Constant fields

You define constants just like variable fields, but include the Const keyword. As with local procedure-level constants, you must assign a value to the constant immediately in source code, using literals or simple nonvariable calculations.

Private Const BaseStrengthFactor As Integer = 1
Enumerations

Enumerations define related integral values. Once defined, you can use them in your code just like other integer values.

Private Enum GeneralSuperPower
   Flight
   Speed
   VisionRelated
   HearingRelated
   WaterRelated
   TemperatureRelated
   ToolsAndGadgets
   GreatCostume
End Enum

Enumerations can also be defined at the namespace level, outside any specific class.

Sub methods

Classes include two types of methods: subs and functions. All logic code in your application appears in one of these method types or in properties, so don’t bother looking for such code in an enumeration. Sub methods perform some defined logic, optionally working on data passed in as arguments.

Public Sub DemonstrateMainPower( _
      ByVal strengthFactor As Integer)
   ' ----- Logic code appears here.
End Sub

The DemonstrateMainPower method, as a public member of your class, can be called either by code within the class, or by any code referencing an instance of your class. This method includes a single parameter, strengthFactor, through which calls to the method send in data arguments.

You can jump out of a sub method at any time using the Return statement, or the older pre-.NET Exit Sub statement.

Function methods

Function methods are just like sub methods, but they support a return value. You define the data type of the return value with an As clause at the end of the function definition. You can assign the return value using the Return statement, or by assigning the function name directly within the code.

Public Function GetSecretIdentity( _
      ByVal secretPassword As String) As String
   If (secretPassword = "Krypton") Then
      ' ----- I created a class field named
      '       TrueIdentity earlier.
      Return TrueIdentity
   Else
      GetSecretIdentity = "FORGET IT BAD GUY"
   End If
End Function

If you use the assignment-to-function-name style of return value assignment, use the Exit Function statement to return to the calling code at any time.

Properties

Properties combine the ideas of fields and methods. You can create read-write, read-only, or write-only properties through the Get and Set accessors. The following code defines a write-only property:

Public WriteOnly Property SecretIdentity(  ) As String
   Set(ByVal value As String)
      TrueIdentity = value
   End Set
End Property
Delegates

Delegates define arguments and return values for a method, and encase them in a single object all their own. They are generally used to support the event process, callback procedures, and indirect calls to class methods.

Public Delegate Sub GenericPowerCall( _
   ByVal strengthFactor As Integer)

Since delegates are pretty generic, they are often defined at the namespace level, outside any class definition.

Events

Adding events to your class allows consumers of your class to react to changes and actions occurring within a class instance. The syntax used to define events looks a lot like a method definition, but an alternative syntax uses previously defined delegates to indicate the signature of the event.

' ----- Non-delegate definition.
Public Event PerformPower( _
   ByVal strengthFactor) As Integer

' ----- Delegate definition.
Public Event PerformPower As GenericPowerCall
Declares

The Declare statement lets you call code defined in external DLL files, although it works only with pre-.NET Windows DLL calls. The syntax for declares closely resembles the syntax used to define methods.

Public Declare Function TalkToBadGuy Lib "evil.dll" ( _
   ByVal message As String) As String

Once defined, an externally declared sub or function can be used in your code as though it were a built-in .NET sub or function definition. The .NET Framework does a lot of work behind the scenes to shuttle data between your program and the DLL. Still, care must be taken when interacting with such external “unmanaged” code, especially if the DLL is named evil.dll.

Interfaces

Interfaces allow you to define abstract classes and, in a way, class templates. A section near the end of this chapter discusses interfaces. Interfaces can also be defined at the namespace level, and usually are.

Nested types

Classes can include other subordinate classes (or structures) for their own internal or public use. If you make such a “child” class public, you can return instances of these classes to code that uses the larger “parent” class.

Class Superhero
   Class Superpower
   End Class
End Class

You can nest classes to any depth, but don’t go overboard. Creating multiple classes within the same namespace will likely meet your needs without making the code overly complex. But that’s just my idea; do what you want. It’s your code after all. If you want to throw your life away on a career in the movies, that’s fine with me.

Adding a nice variety of members to a class is a lot of fun. You can add class members in any variety, in any order, and in any quantity. If you add a lot of members, you might even get a quantity discount on Visual Studio from Microsoft, but don’t hold your breath.

Shared Class Members

Normally, objects (class instances) are greedy and selfish; they want to keep everything to themselves and not share with others. That’s why each instance of a class you create has its own version of the data elements defined as class members. Even class methods and properties give the appearance of being distinct for each class instance. It’s as though each object was saying, “I’ve got mine; get your own.” It’s this attitude that has led to what is now commonly called “class warfare.”

In an attempt to promote affability among software components and push for “kinder and gentler” classes, Microsoft included the Shared keyword in its class design. The Shared keyword can be applied to variable field, sub method, function method, and property members of your class. When defined, a shared member can be used without the need to create an instance of that class. You reference these shared members using just the class name and the member name.

Class ClassWithSharedValue
   Public Shared TheSharedValue As Integer
End Class
...later, in some other code...
ClassWithSharedValue.TheSharedValue = 10

Shared members are literally “shared” by all instances of your class, and if public, by code outside the class as well. Since they don’t require an object instance, they are also limited to just those resources that don’t require an object instance. This means that a shared method cannot access a nonshared variable field of the same class. Any class members that are not marked Shared are known as instance members.

Overloaded Members and Optional Arguments

Overloading of a method occurs by attaching the Overloads keyword to each overloaded member.

Class House
   Public Overloads Sub PaintHouse(  )
      ' ----- Use the same color(s) as before.
   End Sub

   Public Overloads Sub PaintHouse(ByVal baseColor As Color)
      ' ----- Paint the house a solid color.
   End Sub

   Public Overloads Sub PaintHouse(ByVal baseColor As Color, _
         ByVal trimColor As Color)
      ' ----- Paint using a main and a trim color.
   End Sub

   Public Overloads Sub PaintHouse(ByVal baseColor As Color, _
         ByVal coats As Integer)
      ' ----- Possibly paint with many coats, of paint
      '       that is, not of fabric.
   End Sub
End Class

When you call the PaintHouse method, you must pass arguments that match one of the overloaded versions. Visual Basic determines which version to use based on the argument signature. If you pass the wrong type or number of arguments, the program will refuse to compile.

Two of the overloaded members in this class look alike, except for the second coats argument.

Public Overloads Sub PaintHouse(ByVal whichColor As Color)

Public Overloads Sub PaintHouse(ByVal baseColor As Color, _
   ByVal coats As Integer)

Instead of defining two distinct methods, I could have combined them into a single method, and defined an optional argument for the coats parameter.

Public Overloads Sub PaintHouse(ByVal baseColor As Color, _
   Optional ByVal coats As Integer = 1)

The Optional keyword can be used on any number of parameters, but no nonoptional parameters can appear after them; the optional arguments must always be last in the list. Although the calling code might not pass a value for coats, .NET still requires that every parameter receive an argument. Therefore, each optional argument includes a default value using a simple assignment within the parameter definition. The optional argument coats uses a default value of 1 through the = 1 clause.

Inheritance

Visual Basic supports inheritance, the joining of two classes in an ancestor-descendant relationship. To implement inheritance, define the base class, and then add the derived class using the keyword Inherits. What a surprise!

Class Animal
   ' ----- Animal class members go here.
End Class

Class Mammal
   Inherits Animal

   ' ----- All members of Animal are automatically
   '       part of Mammal. Add additional Mammal
   '       features here.
End Class

The Inherits statement must appear at the start of the class definition, before the definition for any class members. It must include the name of exactly one other class, the base class. If you split up your derived class using the Partial keyword, you need to use the Inherits statement in only one of the parts. And since a derived class can use only a single base class, you’re pretty much limited to using the Inherits statement only once per class. (The base class can be used in several different derived classes, and the derived class can further be used as a base class for other derived classes.)

Derived classes automatically inherit all defined members of the base class. If a derived class needs to provide special functionality for a member defined in the base class, it overrides that member. This is a two-step process: (1) the base class must allow its member to be overridden with the Overridable keyword; and (2) the derived class must supply the overriding code using the Overrides keyword.

Class Animal
   Public Overridable Sub Speak(  )
      MsgBox("Grrrr.")
   End Sub
End Class

Class Canine
   Inherits Animal
   Public Overrides Sub Speak(  )
      MsgBox("Bark.")
   End Sub
End Class

Any class that derives from Animal can now supply its own custom code for the Speak method. But the same is true for classes derived from Canine; the Overridable keyword is passed down to each generation. If you need to stop this attribute at a specific generation, use the NotOverridable keyword. This keyword is valid only when used in a derived class since base class members are nonoverridable by default.

Class Canine
   Inherits Animal
   Public NotOverridable Overrides Sub Speak(  )
      MsgBox("Bark.")
   End Sub
End Class

There are times when it is not possible to write a truly general method in the base class, and you want to require that every derived class define its own version of the method. Using the MustOverride keyword in the base member definition enables this requirement.

Class Animal
   Public MustOverride Sub DefenseTactic(  )
End Class

Members marked as MustOverride include no implementation code of their own, since it would go unused. (Also notice that DefenseTactic has no closing End Sub statement.) Because there is no code associated with this member, the entire Animal class has a deficiency. If you created an instance of Animal and called its DefenseTactic method, panic would ensue within the application. Therefore, it is not possible to create instances of classes that contain MustOverride members. To note this limitation, the class is also decorated with the MustInherit keyword.

MustInherit Class Animal
   Public MustOverride Sub DefenseTactic(  )
End Class

It won’t be possible to create an instance of Animal directly, although you can derive classes from it, and create instances of those classes. Also, you can create an Animal variable (a reference type) and assign an instance of an Animal-derived class to it.

Dim zooMember As Animal
Dim monkey As New Simian  ' Simian is derived from Animal
zooMember = monkey

Such code doesn’t really seem fair to the base class. I mean, it defined all the core requirements for derived classes, but it doesn’t get any of the credit since it can’t be directly instantiated. But there is a way for a base class to control its own destiny, to take all the glory for itself. It does this with the NotInheritable keyword.

NotInheritable Class Animal
End Class

The only way to use a NotInheritable class is to create an instance of it; you cannot use it as the base class of another derived class. (If your noninheritable class contains shared members, they can be accessed without the need to create an instance.)

Inherits, MustInherit, NotInheritable, Overrides, Overridable, NotOverridable—this certainly isn’t your grandmother’s Visual Basic anymore. And there’s still one more of these inimitable keywords: Shadows. When you override a base class member, the new code must use a definition that is identical to the one provided in the base class. That is, if you override a function method with two String arguments and an Integer return code, the overriding code must use that same signature. Shadowed members have no such requirements. A shadowed member matches an item in the base class “in name only”; everything else is up for grabs. You can even change the member type. If you have a sub method named PeanutButter in a base class, you can shadow it in the derived class with a variable field (or constant, or enumeration, or nested class) also named PeanutButter.

Class Food
   Public Sub PeanutButter(  )
   End Sub
End Class
Class Snack
   Inherits Food
   Public Shadows PeanutButter As String
      ' Hey, it's not even a "Sub"
End Class

Without the Shadows keyword in the Snack class, a compile-time error would occur.

Creating Instances of Classes

Step one: designing classes. Step two: deriving classes. Step three: creating class instances. Step four: cha-cha-cha. Visual Basic uses the New keyword to create instances of your custom classes.

Dim myPet As Animal = New Animal
' ----- Or...
Dim myPet As New Animal
' ----- Or...
Dim myPet As Animal
myPet = New Animal

The instance can then be used like any other .NET instance variable. Member access occurs using “dot” notation.

myPet.Name = "Fido"

You can also (within reason) pass instance variables between their base and derived variations.

Dim myPet As Animal
Dim myDog As Canine
myDog = New Canine
myDog.Name = "Fido"
myPet = myDog       ' Since Canine derives from Animal
MsgBox(myPet.Name)  ' Displays "Fido"

If you have Option Strict set to On, there will be limits on your ability to convert between types, especially narrowing conversions (where the source data type will not always “fit” in the target variable). In such cases, you must use the CType function (or one of a few similar .NET and Visual Basic supplied functions) to enable the conversion.

myDog = CType(myPet, Canine)

Referring to class instances is simply a matter of referring to the variable or object that contains the instance. That is true for code that uses an instance from outside the class itself. For the code within your class (such as in one of its methods), you refer to members of your instance as though they were local variables (with no qualification), or use the special Me keyword.

Class Animal
   Public Name As String
   Public Sub DisplayName(  )
      ' ----- Either of these lines will work.
      MsgBox(Name)
      MsgBox(Me.Name)
   End Sub
End Class

A similar keyword, MyClass, usually acts like the Me keyword, but it has some different functionality when a class instance is stored in a variable from a different (base or derived) class type. If you create an instance of Canine, but store it in an Animal variable, references using Me will focus on the Canine code, whereas references to MyClass will focus on the Animal code. I won’t be using MyClass in the Library Project, and for most simple uses of class instances, you will never use it either. But there are times when it is important to differentiate between base and derived code, and this is the way to do it.

The MyBase keyword references elements of the base class from which the current class derives. It references only the closest base class; if you have a class named Class5 that derives from Class4, which in turn derives from Class3, which derives from Class2, which derives from Class1, which eventually derives from System.Object, references to MyBase in the code of Class5 refer to Class4. Well, that’s almost true. If you try to use MyBase.MemberName, and MemberName doesn’t exist in Class4, MyBase will search back through the stack of classes until it finds the closest definition of MemberName.

Class Animal
   Public Overridable Sub ObtainLicense(  )
      ' ----- Perform Animal-specific licensing code.
   End Sub
End Class

Class Canine
   Inherits Animal
   Public Overrides Sub ObtainLicense(  )
      ' ----- Perform Canine-specific licensing code, then...
      MyBase.ObtainLicense(  )  ' Calls code from Animal class
   End Sub
End Class

Constructors and Destructors

Class instances have a lifetime: a beginning, a time of activity, and finally, thankfully, an end. The beginning of an object’s lifetime occurs through a constructor; its final moments are dictated by a destructor before passing into the infinity of the .NET garbage collection process.

Each class includes at least one constructor, whether explicit or implicit. If you don’t supply one, .NET will at least perform minimal constructor-level activities, such as reserving memory space for each instance variable field of your class. If you want a class to have any other startup-time logic, you must supply it through an explicit constructor.

Constructors in Visual Basic are sub methods with the name New. A New constructor with no arguments acts as the default constructor, called by default whenever a new instance of a class is needed.

Class Animal
   Public Name As String
   Public Sub New(  )
      ' ----- Every animal must have some name.
      Name = "John Doe of the Jungle"
   End Sub
End Class

Without this constructor, new instances of Animal wouldn’t have any name assigned to the Name field. And since String variables are reference types, Name would have an initial value of Nothing. Not very user-friendly. A default constructor gives you a chance to provide at least the minimum needed data and logic for a new instance.

You can provide additional custom constructors by adding more New methods, each with a different argument signature.

Class Animal
   Public Name As String
   Public Sub New(  )
      ' ----- Every animal must have some name.
      Name = "John Doe of the Jungle"
   End Sub
   Public Sub New(ByVal startingName As String)
      ' ----- Use the caller-supplied name.
      Name = startingName
   End Sub
   Public Sub New(ByVal startingCode As Integer)
      ' ----- Build a name from a numeric code.
      Name = "Animal Number " & CStr(startingCode)
   End Sub
End Class

The following code demonstrates each constructor:

MsgBox((New Animal).Name)
   ' Displays "John Doe of the Jungle"

MsgBox((New Animal("Fido")).Name)
   ' Displays "Fido"

MsgBox((New Animal(5)).Name)
   ' Displays "Animal Number 5"

You can force the consumer of your class to use a custom constructor by excluding a default constructor from the class definition. Also, if you’re deriving your class from anything other than System.Object, it’s usually a good idea to call the base class’s constructor as the first line of your derived constructor, although the default constructor in the base class will be called . . . by default.

Class Canine
   Inherits Animal
   Public Sub New(  )
      MyBase.New(  )  ' Calls Animal.New(  )
      ' ----- Now add other code.
   End Sub
End Class

Killing a class instance is not as easy as it might seem. When you create local class instances in your methods, they are automatically destroyed when that method exits if you haven’t assigned the instance to a variable outside the method. If you create an instance in a method and assign it to a class member, it will live on in the class member for the lifetime of the class, even though the method that created it has exited.

But let’s think only about local instances for now. An instance is destroyed when the routine exits. You can also destroy an instance immediately by setting its variable to Nothing.

myDog = Nothing

Setting the variable to a new instance will destroy any previous instance stored in that variable.

myDog = New Canine
myDog.Name = "Fido"
myDog = New Canine  ' Sorry Fido, you're gone

When an object is destroyed, .NET calls a special method named Finalize, if present, to perform any final cleanup before removing the instance from memory. Finalize is implemented as a Protected method of the base System.Object class; you must override this method in your class to use it.

Class Animal
   Protected Overrides Sub Finalize(  )
      ' ----- Cleanup code goes here. Be sure to call the
      '       base class's Finalize method.
      MyBase.Finalize(  )
   End Sub
End Class

So, what’s with that crack about killing instances being so hard? The problem is that .NET controls the calling of the Finalize method; it’s part of the garbage collection process. The framework doesn’t continually clean up its garbage. It’s like the service at your house; it gets picked up by the garbage truck only once in a while. Until then, it just sits there, rotting, decaying, decomposing, and not having its Finalize method called. For most objects, this isn’t much of a problem; who cares if the memory for a string gets released now or 30 seconds from now. But there are times when it is important to release acquired resources as quickly as possible. For instance, if you acquire a lock on an external hardware resource and release it only in the destructor, you could be holding that lock long after the application has exited. Talk about a slow death.

There are two ways around this problem. One way is to add a separate cleanup method to your class that you expect any code using your class to call. This will work—until some code forgets to call the method. (You should therefore also call this routine from the Finalize destructor.) The second method is similar, but it uses a framework-supplied interface called IDisposable. (I’ll talk about interfaces in a minute, so don’t get too worried about all the code shown here.)

Class Animal
   Implements IDisposable

   Protected Overrides Sub Finalize(  )
      ' ----- Cleanup code goes here. Be sure to call the
      '       base class's Finalize method.
      MyBase.Finalize(  )
   End Sub

   Public Overloads Sub Dispose(  ) _
         Implements IDisposable.Dispose
      ' ----- Put cleanup code here. Also make these calls.
      MyBase.Dispose(  )   ' Only if base class is disposable.
      System.GC.SuppressFinalize(Me)
   End Sub
End Class

The SuppressFinalize method tells the garbage collector, “Don’t call Finalize; I’ve already cleaned up everything.” Any code that uses your class will need to call its Dispose method to perform the immediate cleanup of resources. So, it’s not too different from the first way I talked about, but it does standardize things a bit. Also, it enables the use of the Visual Basic Using statement. This block statement provides a structured method of cleaning up resources:

Using myPet As New Animal
   ' ----- Code here uses myPet.
End Using
' ----- At this point, myPet is destroyed, and Dispose is
'       called automatically by the End Using statement.

Interfaces

The MustOverride and MustInherit keywords force derived classes to implement specific members of the base class. But what if you want the derived class to implement all members of the base class? You could use MustOverride next to each method and property, but a better way is to use an interface. Interfaces define abstract classes, classes consisting only of definitions, no implementation. (OOP purists will point out that a class with even just one MustOverride flag is also an abstract class. Fine.) Interfaces create a contract, an agreement that the implementing class or structure agrees to carry out.

The Interface statement begins the interface definition process. By convention, all interface names begin with the capital letter I.

Interface IBuilding
   Function FloorArea(  ) As Double
   Sub AlterExterior(  )
End Interface

As you see here, the syntax is a somewhat simplified version of the class definition syntax. All interface members are automatically public, so access modifiers aren’t included. Only the definition line of each member is needed since there is no implementation. In addition to function and sub methods, interface members also include properties, events, other interfaces, classes, and structures. Interfaces can also derive from other interfaces (using the Inherits keyword), and automatically include all the members of the base interface.

You attach interfaces to a class using the Implements keyword. This same keyword is used later to indicate which class member defines which interface member.

Class House
   Implements IBuilding

   Public Function FloorArea(  ) As Double _
         Implements IBuilding.FloorArea
      ' ----- Add implementation here.
   End Function

   Public Sub PaintHouse(  ) Implements IBuilding.AlterExterior
      ' ----- Add implementation here.
   End Sub
End Class

Class implementations of interface members are not required to maintain the original interface member name (although the argument signature must match the one in the interface). In the sample code, FloorArea kept the name of the equivalent interface member, but the AlterExterior member was implemented using the PaintHouse method. This makes possible some interesting code.

Dim someHouse As New House
Dim someBuilding As IBuilding
someBuilding = someHouse
someBuilding.AlterExterior(  )  ' Calls someHouse.PaintHouse(  )

Classes can only inherit from a single base class, but there is no limit on the number of interfaces that a class can implement.

Class House
   Implements IBuilding, IDisposable

Also, a single class member can implement multiple interface members.

Public Sub PaintHouse(  ) Implements _
   IBuilding.AlterExterior, IContractor.DoWork

So, why use interfaces? Interfaces provide a generic way to access common functionality, even among objects that have nothing in common. Classes named Animal, House, and Superhero probably have nothing in common in terms of logic, but they may all need a consistent way to clean up their resources. If they each implement the IDisposable interface, they gain that ability without the need to derive from some common base class.

Modules and Structures

In addition to classes, Visual Basic provides two related object definition features: structures and modules. Although they have different names than “class,” they still act a lot like classes, but with different features enabled or disabled.

Modules provide a place to include general global code and data values in your application or assembly. All members of a module are Shared. In fact, a module acts just like a class with the Shared keyword added to each member, yet with one major difference: no inheritance relationship is allowed with modules. You cannot create a derived module from a base class or module, nor can you use a module as a base for any other type. Modules are a carryover from pre-.NET versions of Visual Basic, which included “Modules” for all non-Form code.

Friend Module GenericDataAndCode
   ' ----- Application-global constant.
   Public Const AllDigits As String = "0123456789"

   ' ----- Application-global function.
   Public Function GetEmbeddedDigits( _
         ByVal sourceString As String) As String
   End Sub
End Module

You cannot create an instance of a module. As with classes, modules appear in the context of a namespace. Unlike a class with shared members, you do not need to specify the module name to use the module member. All module members act as global variables and methods, and can be used immediately in any other code in your application without further qualification. (You can restrict a module member’s use to just the module by declaring the member as Private.)

Structures are much more like classes than are modules. Classes implement reference types, but structures implement value types. All structures derive from System.ValueType (which in turn derives from System.Object). As such, they act like the core Visual Basic data types, such as Integer. You can create instances of a structure using the same syntax used to create class instances. However, you cannot use a structure as the base for another derived structure. And although you can include a constructor in your structure, destructors are not supported.

Because of the way that structures are stored and used in a .NET application, they are well suited to simple data types. You can include any number of members in your structure, but it is best to keep things simple.

Partial Methods

Earlier in the chapter I wrote about partial classes, the ability to divide a class into multiple files. Partial classes are especially common in code created by code generators. Visual Studio is, in part, a code generator; as you drag-and-drop controls on your form, it generates code for you in a partial Form class. In such cases, partial classes have two authors: the automated generator and you.

Partial methods, new in Visual Basic 2008, are also used by code generators, although you are free to employ them yourself. They are particularly useful when some automatically generated class wants to give its second author (you) the ability to supply some optional logic that will enhance the automatically generated logic. Partial methods might be more accurately called “optional methods,” since you have the option to implement them or not.

Partial methods have two parts: (1) an unimplemented half; and (2) an optional implemented half. The two halves appear in different parts of a partial class. A partial method is never split between a base and derived class; they have nothing to do with inheritance.

The unimplemented half of a partial method looks like an empty sub method definition, but with the Partial keyword added.

Partial Private Sub ImplementIfYouDare(  )
End Sub

Partial methods must always be sub methods, never functions, and they must always be declared as Private. If you supply any parameters, they must always be decorated with the ByVal keyword, not ByRef. Boy, that’s a lot of restrictions.

The implemented half looks really familiar, except for the lack of a “Partial” prefix. But it sure looks good with real code between its jaws.

Private Sub ImplementIfYouDare(  )
   MsgBox("I did it, so there.")
End Sub

So, what’s the big deal with these partial methods? Perhaps not much, but looking at an example might help. Let’s return to our living, breathing Animal class, this time with a partial method included. Let’s start with the auto-generated side of the world.

Partial Class Animal
   Public Sub Move(  )
      ' ----- Interesting movement code, then...
      MoveSideEffects(  )
   End Sub

   Partial Private Sub MoveSideEffects(  )
   End Sub
End Class

Sometimes when an animal moves, it has side effects, such as scaring other animals. As the second half of the implementation team, you could program these side effects by completing the other half of the partial method. But if there were no side effects for this particular implementation, you could just leave the partial method unfinished. It’s optional.

Yawn, yawn, snore, snore. “Get to the point, Tim,” you say. The point is that if you never write the second half of a partial method, the Visual Basic compiler will leave out both halves, generating code as though the unimplemented half was never auto-generated in the first place. So, that earlier Animal class becomes:

Partial Class Animal
   Public Sub Move(  )
      ' ----- Interesting movement code, then...
   End Sub
End Class

Not only did the partial method definition disappear, but the call to that method inside the Move routine disappeared as well.

As a lone programmer writing lonely code, you will probably never craft a partial method; the event system is a much better way to generically respond to actions within a class. But you might have a chance to write the implementation side of a partial method. Partial methods will be used in some LINQ-specific code, especially when designing LINQ code that communicates with SQL Server. But more on that in Chapter 17.

Related Issues

Let me take a few moments here before getting into the project code to discuss some issues that don’t really fit into any particular chapter discussion, but that you might end up using a lot in your own applications.

The MsgBox Method

Although I’ve used it on practically every page of this book so far, I have never formally introduced you to the MsgBox method. Part of the Microsoft.VisualBasic namespace, MsgBox is a carryover from the MsgBox function in the original release of Visual Basic. It displays a simple message window, including a selection of response buttons and an optional icon. As a function, it returns a code indicating which button the user clicked to close the form, one of the MsgBoxResult enumeration values. The syntax is:

Public Function MsgBox(ByVal Prompt As Object, _
   Optional ByVal Buttons As MsgBoxStyle = MsgBoxStyle.OKOnly, _
   Optional ByVal Title As Object = Nothing) As MsgBoxResult

The Prompt parameter accepts a string for display in the main body of the dialog; Buttons indicates which buttons, icons, and other settings to use when displaying the dialog; and Title accepts a custom window title if you want something other than the application title to appear. The following statement displays the window in Figure 8-2:

Communicating an important message

Figure 8-2. Communicating an important message

Dim result As MsgBoxResult = MsgBox( _
   "It's safe to click; the computer won't explode.", _
   MsgBoxStyle.YesNoCancel Or MsgBoxStyle.Question, _
   "Click Something")

The MsgBox function is considered to be an intrinsic part of the language. But as a member of the Microsoft.VisualBasic namespace, it’s generally used only within the Visual Basic language. If you were to do some .NET coding in C#, you would normally opt instead for the MessageBox.Show method. It works pretty much like the MsgBox function, but its second and third arguments are reversed. Some .NET conformists insist that MsgBox—and anything that appears in the Microsoft.VisualBasic namespace—must be spurned in favor of class library alternatives. I find it to be a preference choice, but you may encounter just such a person insisting that your code is substandard. You can read my views about such tactics in Chapter 26.

If you plan to develop Visual Basic code that targets Microsoft’s Silverlight platform, avoiding Microsoft.VisualBasic can bring about improved performance when downloading your assembly to the client workstation. Silverlight applications benefit greatly from reductions in compiled code size, at least for download purposes. Anything you can do to eliminate dependencies on external assemblies such as Microsoft.VisualBasic will help speed your program along.

Using DoEvents

Programs are designed to do a lot of thinking, and sometimes they think so much, they pretty much lock up the computer. This is especially true of Visual Basic methods that perform a lot of database-heavy transactions, one right after another. The system defers less important screen updates so that more important data processing code can occur first. That’s great, but sometimes the user thinks, “This stupid computer’s dead again,” and pulls the plug. If the screen would simply provide better updates, the user might be more patient.

Each control on your form (and the form itself) includes a Refresh method, but it can be a bother to constantly refresh everything. And refreshing the display wouldn’t do much to enable the “Cancel” button that you want your user to click to abort all that lovely data processing. To make life easier, Visual Basic includes a DoEvents method. When this method is called, the current method’s code pauses temporarily, and messages in the thread’s incoming message queue are processed, including “paint” (screen update) messages. DoEvents is part of the My namespace, and is used as a standalone statement:

My.Application.DoEvents(  )

Be warned that overuse of DoEvents can slow down your application, and can lead to problems related to an event being called too many times. In general, it should be used only in a processing-intensive block of code, and then it should be spread out so that it is called only a few times per second at the most.

ParamArray Arguments

Any method can enable optional arguments, and the calling code can choose to include or exclude those arguments. But what if you wanted to add an unlimited number of optional arguments to a method? How could you write, for instance, a function that would return the average of all supplied arguments, with no limits on the number of arguments? Although you could accept an array variable with the source data values, you could also use a parameter array argument, also called a ParamArray argument.

As with optional arguments, ParamArray arguments must appear at the end of a method’s argument list, and there can be only one, because one is more than enough for any method. Parameter array arguments use the ParamArray keyword just before the argument name.

Public Function CalculateAverage( _
      ParamArray sourceData(  ) As Decimal) As Decimal
   ' ----- Calculate the average for a set of numbers.
   Dim singleValue As Decimal
   Dim runningTotal As Decimal = 0@

   If (sourceData.GetLength(0) = 0) Then
      Return 0@
   Else
      For Each singleValue In sourceData
         runningTotal += singleValue
      Next singleValue
      Return runningTotal / sourceData.GetLength(0)
   End If
End Function

Calls to the CalculateAverage function now accept any number of decimal values.

MsgBox(CalculateAverage(1, 2, 3, 4, 5))  ' Displays: 3

Summary

The ability to extend classes through inheritance is truly the foundation on which complex yet manageable programs are built in .NET. And they are not overly complex, either. Classes are simple containers for their members, and the variety and complexity of the available members are not that great. So, it’s really amazing that you can write almost any type of program, and implement any number of features, using these simple foundational tools. Oh yeah, the Visual Basic language helps, too.

As we add code to the Library Project throughout this book, you will become more and more familiar with classes, structures, modules, and their members. And although you’ll never remember whether ByRef or ByVal is the default parameter-passing mechanism for methods, you will add properties, methods, events, fields, and other types to classes like you were born with the ability.

Project

This chapter’s code implements two features of the Library Project: (1) a simple helper class used with ListBox and ComboBox controls to manage text and data; and (2) a set of generic forms used to edit lookup tables in the Library, such as tables of status codes.

Supporting List and Combo Boxes

In Visual Basic 6.0 and earlier, ListBox and ComboBox controls included two primary array-like collections: List (used to store the display text for each item) and ItemData (used to store a 32-bit numeric value for each item). The List array was important to the user since it presented the text for each item. But many programmers depended more on the ItemData array, which allowed a unique identifier to be attached to each list item.

cboMonth.AddItem "January"
cboMonth.ItemData(cboMonth.NewIndex) = 1
cboMonth.AddItem "February"
cboMonth.ItemData(cboMonth.NewIndex) = 2
...
cboMonth.AddItem "December"
cboMonth.ItemData(cboMonth.NewIndex) = 12

Later, after the user selected a value from the list, the numeric ID could be used for database lookup or any other designated purpose.

nMonth = cboMonth.ItemData(cboMonth.ListIndex)

The bad news is that neither List nor ItemData exists in the .NET variation of ListBox or ComboBox controls. The good news is that both are replaced with a much more flexible collection: Items. The Items collection stores any type of object you want: instances of Integer, String, Date, Animal, or even Superhero, and you can mix them within a single ListBox. Since Items is just a collection of System.Object instances, you can put any type of object you wish in the collection. The ListBox (or ComboBox) uses this collection to display items in the list.

So, how does a ListBox control know how to display text for any mixture of objects? By default, the control calls the ToString method of the object. ToString is defined in System.Object, and you can override it in your own class. The ListBox control also includes a DisplayMember property that you can set to the field or property of your class that generates the proper text.

Let’s see a ListBox in action. Add a new ListBox to a form, and then add the following code to the form’s Load event handler.

Public Class Form1
   Private Sub Form1_Load(ByVal sender As System.Object, _
         ByVal e As System.EventArgs) Handles MyBase.Load
      ListBox1.Items.Add(1)
      ListBox1.Items.Add("Easy")
      ListBox1.Items.Add(#5/3/2006#)
   End Sub
End Class

Running this code displays the form in Figure 8-3.

A simple ListBox with three different items

Figure 8-3. A simple ListBox with three different items

For the old ItemData value, the ListBox control includes a ValueMember property that identifies the identifier field or property for the objects in the Items collection. But you don’t have to use ValueMember. Instead, you can simply extract the object in question from the Items collection, and examine its members with your own custom code to determine its identity. In reality, it’s a little more work than the old Visual Basic 6.0 method. But then again, since you can store objects of any size in the Items collection, you could opt to store entire database records, something you could never do before .NET.

Still, storing entire records in a ListBox or ComboBox control is pretty wasteful. It’s usually much better to store just an ID number, and use it as a lookup into a database. That’s what we’ll do in the Library Project. To support this, we’ll need to create a simple class that will expose a text and data value. First, let’s go back into the Library code.

PROJECT ACCESS

Load the Chapter 8 (Before) Code project, either through the New Project templates or by accessing the project directly from the installation directory. To see the code in its final form, load Chapter 8 (After) Code instead.

Let’s put the class in a source code file all its own. Add a new class file through the Project → Add Class menu command. Name the file ListItemData.vb and click the Add button. The following code appears automatically:

Public Class ListItemData

End Class

This class will be pretty simple. It will include only members for text and item display. In case we forget to connect the text field to the ListBox or ComboBox’s DisplayMember property, we’ll also include an override to the ToString function, plus a custom constructor that makes initialization of the members easier. Add the following code to the body of the class.

INSERT SNIPPET

Insert Chapter 8, Snippet Item 1.

Public ItemText As String
Public ItemData As Integer

Public Sub New(ByVal displayText As String, _
      itemID As Integer)
   ' ----- Initialize the record.
   ItemText = displayText
   ItemData = itemID
End Sub

Public Overrides Function ToString(  ) As String
   ' ----- Display the basic item text.
   Return ItemText
End Function

Public Overrides Function Equals(ByVal obj As Object) _
      As Boolean
   ' ----- Allow IndexOf(  ) and Contains(  ) searches by ItemData.
   If (TypeOf obj Is Integer) Then
      Return CBool(CInt(obj) = ItemData)
   Else
      Return MyBase.Equals(obj)
   End If
End Function

Later, when it’s time to populate a ListBox, we can use this object to add the display and identification values.

ListBox1.Items.Add(New ListItemData("Item Text", 25))

The override of the Equals method allows us to quickly look up items already added to a ListBox (or similar) control using features already included in the control. The ListBox control’s Items collection includes an IndexOf method that returns the position of a matching item. Normally, this method will match only the object itself; if you pass it a ListItemData instance, it will report whether that item is already in the ListBox. The updated Equals code will also return True if we pass an Integer value that matches a ListItemData.ItemData member for an item already in the list.

Dim itemPosition As Integer = SomeListBox.Items.IndexOf(5)

Editing Code Tables

Back in Chapter 4, when we crafted the database for the Library Project, several of the tables were created to fill simple ComboBox lists in the application. All of these tables begin with the prefix “Code,” and contain records that rarely, if ever, change in the lifetime of the application. One such table is CodeCopyStatus, which identifies the current general condition of an item in the library’s collections.

Field

Type

Description

ID

Long - Auto

Primary key; automatically assigned. Required.

FullName

Text(50)

Name of this status entry. Required.

Since all of these tables have basically the same format—an ID field and one or more content fields—it should be possible to design a generic template to use for editing these tables. A base (class) form would provide the basic editing features, to be developed in full through derived versions of the base form.

For the project, we will add two forms: a “summary” form (that displays a list of all currently defined codes) and a “detail” form (that allows editing of a single new or existing code). To make things even simpler, we will include only the most basic record-management functionality in the summary form. Most of the code needed to edit, display, and remove codes will appear in the detail forms.

The Generic Detail Form

Add a new form to the project (Project → New Windows Form), naming it BaseCodeForm.vb. Alter the following properties as indicated.

Property

Setting

(Name)

BaseCodeForm

ControlBox

False

FormBorderStyle

FixedDialog

ShowInTaskbar

False

Size

406, 173

StartPosition

CenterScreen

Text

Code Form

Now access the source code for this class (View → Code). The code will never create instances of this generic form directly, so let’s disallow all direct instantiation by including the MustInherit keyword.

Public MustInherit Class BaseCodeForm

End Class

The main features of the form will be the adding of new code records, the editing of existing code records, and the removal of existing records. Add three function skeletons that support these features. We could have made them MustOverride, but as you’ll see later, we will want the option to keep the default functionality from the base generic form.

INSERT SNIPPET

Insert Chapter 8, Snippet Item 2.

Public Overridable Function AddRecord(  ) As Integer
   ' ----- Prompt to add a new record. Return the ID
   '       when added, or −1 if cancelled.
   Return −1
End Function

Public Overridable Function DeleteRecord( _
      ByVal recordID As Integer) As Boolean
   ' ----- Prompt the user to delete a record.
   '       Return True on delete.
   Return False
End Function

Public Overridable Function EditRecord( _
      ByVal recordID As Integer) As Integer
   ' ----- Prompt the user to edit the record. Return the
   '       record's ID if saved, or −1 on cancel.
   Return −1
End Function

The detail form will take responsibility for filling the ListBox control on the summary form with its items. Two methods will handle this: one that adds all items, and one that updates a single item. The derived class will be required to supply these features.

INSERT SNIPPET

Insert Chapter 8, Snippet Item 3.

' ----- Fill a ListBox control with existing records.
Public MustOverride Sub FillListWithRecords( _
   ByRef destList As ListBox, ByRef exceededMatches As Boolean)

' ----- Return the formatted name of a single record.
Public MustOverride Function FormatRecordName( _
   ByVal recordID As Integer) As String

The detail form must also display the proper titles and usage information on the summary form.

INSERT SNIPPET

Insert Chapter 8, Snippet Item 4.

' ----- Return a description of this editor.
Public MustOverride Function GetEditDescription(  ) As String

' ----- Return the title-bar text for this editor.
Public MustOverride Function GetEditTitle(  ) As String

Although most of the tables will supply a short list of alphabetized codes, some tables will include a large number (possibly thousands) of codes. The summary form will support a search method, to locate an existing code quickly. Since only certain derived forms will use this feature, we won’t include MustOverride.

INSERT SNIPPET

Insert Chapter 8, Snippet Item 5.

Public Overridable Sub SearchForRecord( _
      ByRef destList As ListBox, _
      ByRef exceededMatches As Boolean)
   ' ----- Prompt the user to search for a record.
   Return
End Sub

Finally, the detail form will indicate which of the available features can be used from the summary form. The summary form will call each of the following functions, and then enable or disable features as requested.

INSERT SNIPPET

Insert Chapter 8, Snippet Item 6.

Public Overridable Function CanUserAdd(  ) As Boolean
   ' ----- Check the security of current user to see
   '       if adding is allowed.
   Return False
End Function

Public Overridable Function CanUserEdit(  ) As Boolean
   ' ----- Check the security of the user to see
   '       if editing is allowed.
   Return False
End Function

Public Overridable Function CanUserDelete(  ) As Boolean
   ' ----- Check the security of the user to see
   '       if deleting is allowed.
   Return False
End Function

Public Overridable Function UsesSearch(  ) As Boolean
   ' ----- Does this editor support searching?
   Return False
End Function

That’s it for the generic detail form. Later on in the book, we’ll create derived versions for each of the code tables.

The Generic Summary Form

The summary form is a little more straightforward, since it is just a plain form. When it starts up, it uses an instance of one of the derived detail forms to control the experience presented to the user. I’ve already added the form to the project; it’s called ListEditRecords.vb, and it looks like Figure 8-4.

The Generic Summary form

Figure 8-4. The Generic Summary form

A large ListBox control fills most of the form, a control that will hold all existing items. There are also buttons to add, edit, delete, and search for items in the list. There’s a lot of code to manage these items; I’ve already written it in a code snippet. Switch to the form’s source code view, and add the source code just after the Public Class ListEditRecords line.

INSERT SNIPPET

Insert Chapter 8, Snippet Item 7.

The first line of the added code defines a private instance of the generic detail form we just designed.

Private DetailEditor As Library.BaseCodeForm

This field holds an instance of a class derived from BaseCodeForm. That assignment appears in the public method ManageRecords.

Public Sub ManageRecords(ByRef UseDetail _
      As Library.BaseCodeForm)
   ' ----- Set up the form for use with this code set.
   Dim exceededMatches As Boolean
   DetailEditor = UseDetail
   RecordsTitle.Text = DetailEditor.GetEditTitle(  )
   RecordsInfo.Text = DetailEditor.GetEditDescription(  )
   Me.Text = DetailEditor.GetEditTitle(  )
   ActAdd.Visible = DetailEditor.CanUserAdd(  )
   ActEdit.Visible = DetailEditor.CanUserEdit(  )
   ActDelete.Visible = DetailEditor.CanUserDelete(  )
   ActLookup.Visible = DetailEditor.UsesSearch(  )
   DetailEditor.FillListWithRecords(RecordsList, _
      exceededMatches)
   RefreshItemCount(exceededMatches)
   Me.ShowDialog(  )
End Sub

The code that calls ManageRecords passes in a form instance, one of the forms derived from BaseCodeForm. Once assigned to the internal DetailEditor field, the code uses the public features of that instance to configure the display elements on the summary form. For instance, the detail form’s CanUserAdd function, which sports a Boolean return value, sets the Visible property of the ActAdd button. The FillListWithRecords method call populates the summary ListBox control with any existing code values. After some more display adjustments, the Me.ShowDialog method displays the summary form to the user.

Although the user will interact with the controls on the summary form, most of these controls defer their processing to the detail form, DetailEditor. For example, a click on the Add button defers most of the logic to the detail form’s AddRecord method. The code in the summary form doesn’t do much more than update its own display fields.

Private Sub ActAdd_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles ActAdd.Click
   ' ----- Let the user add a record.
   Dim newID As Integer
   Dim newPosition As Integer

   ' ----- Prompt the user.
   newID = DetailEditor.AddRecord(  )
   If (newID = −1) Then Return

   ' ----- Add this record to the list.
   newPosition = RecordsList.Items.Add( _
      (New Library.ListItemData( _
      DetailEditor.FormatRecordName(newID), newID)))
   RecordsList.SelectedIndex = newPosition
   RefreshButtons(  )
   RefreshItemCount(False)
End Sub

Most of the remaining code in the summary form either is just like this (for edit, delete, and search features), or is used to refresh the display based on user interaction with the form. Be sure to examine the code to get a good understanding of how the code works. In later chapters, when we add actual detail forms, we’ll see this code in action.

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

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