Introduction to Object-Oriented Programming
Object-oriented programming is a type of programming that relates coding constructs to objects. The objects that are created in code can have similar characteristics to objects in the real world. You define properties for an object to define its characteristics. For example, light bulbs might have a color property. The value of this property might differ for each individual light bulb; some might be white, and others yellow.
You define methods for an object to describe the actions it can take. Using our light bulb example, the methods might be TurnOn, Adjust-Brightness, and TurnOff. You can define event handlers for an object so that certain actions are performed when a particular event on the object occurs. For example, if a BeforeTurnOff event is raised, it would enable you to first decrease the brightness of the light bulb before turning it off. You also define the type of data the object can contain and any logic that is required to manipulate the data.
Understanding Classes and Objects
Classes contain methods, events, and properties that enable access to data. These methods, events, and properties of a class are known as its members. In VBA, you use procedural programming, writing most of your code within a code module (although classes are available in VBA). In object-oriented programming, most, if not all, of your code is contained within classes.
A class is often described as being a sort of cookie cutter or blueprint for an object. You can also think of a class as a template for an object.
Think about how you would use a Word template. You can add boilerplate text and special formatting (styles) to the template. Then when a document is created based on that template, the new document contains the same characteristics that were applied to the template. The document has access to all the styles defined in the template, but an end user can make additional changes to the document and apply styles to different sections of the document. In this same way, a class contains the base functionality of an object, and yet you can later change properties of each object to make them different. Although a class contains a base set of functionality such as methods, properties, and events, these class members can be used, and the data associated with the object can be accessed, only when you've created an instance of the class. An object is an instance of a class, and the process is known as instantiation.
You learned in Chapter 1 about the extended objects that VSTO provides for Word and Excel. One of these is a NamedRange. A NamedRange is actually a class, and each time you add one to the worksheet, you are creating an instance of that class, or a NamedRange object. VSTO generates a unique name for each instance by appending an incremental number to the end of the class name: NamedRange1, NamedRange2, and so on. If you want to provide a different name, you can make the change in the Properties window. If you also change the value of the properties of NamedRange1—such as setting a specific font or adding a border—then the appearance of NamedRange1 will differ from that of NamedRange2. Even though each NamedRange is unique, the two of them share the same characteristics (properties) because both of them were created from the same NamedRange class.
When you create a VSTO application using Visual Basic 2005, you have numerous objects to work with. There are objects such as Windows Forms and controls, and there are Word, Excel, and Outlook objects such as documents, list objects, and e-mail items. Additionally, the .NET Framework contains a class library that you can use to create objects in your code.
You use the New keyword to create an instance of a class. In the case of a NamedRange, VSTO automatically creates the instance of the class in the auto-generated code of the worksheet's hidden partial class whenever you add a NamedRange (or any other control) to the worksheet. When you create your own class, you can store data privately; to do that, you create variables, known as private member variables, to store data. Then you create public properties so that the data can be accessed by other methods outside the class. This gives you complete control over the access to this data.
To create a property, you add a Property statement. Private member variables are accessible only from outside the class when the Get and Set property procedures are accessed. These private member variables are also known as fields. The Get property procedure returns the value of the field, and the Set property procedure enables you to assign a value to the field. You can create a property in Visual Basic by typing the Property statement, such as the following, and then pressing the ENTER key.
Property Text() as String
Visual Basic automatically creates the Get and Set statements for you, as the code example in Listing 3.1 shows.
Listing 3.1. Creating a property
Property Text() As String Get End Get Set(ByVal value As String) End Set End Property
Notice that the value field is created for you as a parameter of the Set property procedure. To assign the value to the member variable in the Set property procedure, you must create a member variable and write code. You must also write code to return the member variable in the Get property procedure. You can create a read-only property by using the ReadOnly keyword before the property. In the case of a read-only property, you need only provide a Get property procedure.
So far, the Text property you created lets you set and get a value for the Text property. You cannot use these properties or store any data in the class until you have actually created an instance of the class (an object). Each object that you create can hold a different value for the Text property.
In this section you will create a simple Sentence class in a Word solution. As in Chapter 1, you will save the VSTO solutions that are described in this book in the Samples directory at the root of the C:\ drive.
- Open Visual Studio 2005.
- On the File menu, point to New and then click Project.
- In the New Project dialog box, select Word Document in the Templates pane.
- Name the solution SampleDocument, and set the location to C:\Samples.
- In the Visual Studio Tools for Office Project Wizard, select Create a New Document, and then click OK.
- In Solution Explorer, right-click the solution node, point to Add, and select New Item.
In the New Item dialog box, select Class, name the class Sentence.vb, and click Add.
Visual Studio creates the Sentence class and then opens the class file in code view.
- Add the code in Listing 3.2 to the Sentence class.
Listing 3.2. Creating a property named Text for the Sentence class
Public Class Sentence Private TextValue as String Property Text() As String Get Return TextValue End Get Set (ByVal value As String) TextValue = value End Set End Property End Class
The variable TextValue is a private member variable. You can retrieve and set its value only by using the public Text property.
Now that you have a Sentence class, you will create two instances of the class, assigning a different value to the Text property of each class. Finally, you'll retrieve the value of the Text property for each Sentence object and insert it into your document. Follow these steps:
- In Solution Explorer, right-click ThisDocument.vb and select View Code.
- The Code Editor opens, and two default event handlers are visible. The first is the Startup event handler, and the second is the Shutdown event handler for the document.
Add the code in Listing 3.3 to the Startup event handler of ThisDocument.
The code concatenates the text in Sentence1 and Sentence2 and then uses the InsertAfter method to insert the text into the first paragraph of the document. Because the code is added to the ThisDocument class, the Me keyword is used to represent the VSTO document (Microsoft.Office.Tools.Document, which wraps the native Document class).
Listing 3.3. Creating two Sentence objects
Dim Sentence1 As New Sentence() Dim Sentence2 As New Sentence() Sentence1.Text = "This is my first sentence. " Sentence2.Text = "This is my second sentence. " Me.Paragraphs(1).Range.InsertAfter( _ Sentence1.Text & Sentence2.Text)
Press F5 to run the solution.
When the solution runs and the Word document is created, the Startup event handler is raised and two instances of the Sentence class are created. The code then assigns a different string to each Sentence object. Finally, the value of each Sentence object is retrieved and inserted into the first paragraph of the document, as shown in Figure 3.1.
Figure 3.1 Text inserted into the document using the Sentence class
- Stop execution of the solution code by clicking Stop Debugging on the Debug menu, and then close the solution.
Creating and Calling Constructors
As you saw in the example in Listing 3.3, you use the New keyword to create an instance of a class. The New keyword calls the constructor of the class. A class constructor describes how to initialize the properties and methods of the class. Every class has a default constructor that is automatically generated for you as a method called Sub New.
You can override this default constructor by adding your own procedure named Sub New. Constructors can take parameters and can also be overloaded so that you can create an instance of the class in several ways. Overloading means that there are multiple versions of the same method, each with different parameters. Visual Basic 2005 enables you to create constructors that take one or more parameters so that you can pass data when you create an instance of the class. To overload the constructor, you add multiple Sub New methods that take different parameters.
Add the constructors in Listing 3.4 to your Sentence class.
Listing 3.4. Adding two constructors to a class
Public Sub New() TextValue = "Hello World! " End Sub Public Sub New(ByVal myText as String) TextValue = myText End Sub
The first constructor overrides the default parameterless constructor and assigns the text "Hello World" to the member variable, TextValue. If you instantiate the class without passing any text, "Hello World" will be the value of the Sentence class. The second constructor takes a string as a parameter and assigns the string to the member variable.
Replace the code in the Startup event handler of ThisDocument with the code in Listing 3.5.
Listing 3.5. Passing parameters to a constructor
Dim Sentence1 As New Sentence() Dim Sentence2 As New Sentence("This is my second sentence.") Me.Paragraphs(1).Range.InsertAfter( _ Sentence1.Text & Sentence2.Text)
Notice that when you type the open parenthesis after the word Sentence, IntelliSense lists the overloaded methods and displays the required parameter (myText As String) in method 2 of 2, as shown in Figure 3.2.
Figure 3.2 IntelliSense displays a parameter required by the constructor of the Sentence class.
- Press F5 to run the solution.
This time, when the solution runs, the value to be assigned to Sentence2 is passed to the constructor of the Sentence class when the class is instantiated. Although you could assign a value to the Text property of Sentence2 after it's instantiated (as shown in Listing 3.7), this example (Listing 3.5) uses the default value "Hello World!"
You can also add methods to your class to perform an operation on the data. If you want the method to be accessible from the instance of the class, you must declare the method as a public method; to make it accessible from instances of the class in the same assembly, declare it as Friend. Private methods are available only to other members within the class.
- Add the method in Listing 3.6 to your Sentence class. This method calls the ToUpper method of a String, which is provided by the .NET Framework.
Listing 3.6. Creating a public method for a class
Public Sub UpperCase() TextValue = TextValue.ToUpper() End Sub
- Replace the code in the Startup event handler of ThisDocument with the code in Listing 3.7 so that the UpperCase method is called only on Sentence1.
Listing 3.7. Calling the UpperCase method of the Sentence class
Dim Sentence1 as New Sentence() Dim Sentence2 As New Sentence("This is my first sentence.") Sentence1.Text = "This is my first sentence. " Sentence1.UpperCase() Me.Paragraphs(1).Range.InsertAfter( _ Sentence1.Text & Sentence2.Text)
- Press F5 to run the solution.
When the solution runs, the code in Listing 3.6 passes text to the constructor for the second object, but it uses the default (parameterless) constructor for the first object and then reassigns a value to the Text property of Sentence1. After the call to the UpperCase method on the first object, the first sentence that is inserted into the document appears in uppercase, and the second sentence appears in sentence case, as shown in Figure 3.3.
Figure 3.3 Text inserted into the document using the Sentence class
You can add events to your class to indicate that objects created from this class can raise the events you've added.
- Add the code in Listing 3.8 to your Sentence class. This code adds an event statement and replaces the UpperCase method.
Listing 3.8. Creating an event for a class
Public Event CaseChanged() Public Sub UpperCase() TextValue = TextValue.ToUpper() RaiseEvent CaseChanged() End Sub
Replace the code in the Startup event handler of ThisDocument with the code in Listing 3.9 so that a message box is shown when the event is raised.
You can create an event handler for the CaseChanged event by declaring the variables for the Sentence objects with the WithEvents keyword, as shown in Listing 3.9. This code also adds a method that handles the OnChanged event for the Sentence1 and Sentence2 classes. Notice that the Sentence_ChangeCase method lists both Sentence1.CaseChanged and Sentence2.CaseChanged in the Handles clause.
Listing 3.9. Displaying a message box when an event is raised
WithEvents Sentence1 as New Sentence() WithEvents Sentence2 As New Sentence( _ "This is my first sentence.") Private Sub ThisDocument_Startup(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Startup Sentence1.Text = "This is my first sentence. " Sentence1.UpperCase() Me.Paragraphs(1).Range.InsertAfter( _ Sentence1.Text & Sentence2.Text) End Sub Sub Sentence_CaseChange() Handles Sentence1.CaseChanged, _ Sentence2.CaseChanged MsgBox("Case changed.") End Sub
- Press F5 to run the code.
Only one message box is displayed because only Sentence1 called the UpperCase method, which raised the CaseChange event. If you add code to call UpperCase on Sentence2, the event will be raised on both objects, and therefore two messages will be displayed.
Partial classes are a new feature in .NET Framework 2.0 and are supported in Visual Basic 2005. The Partial keyword enables you to split a class into separate source files. You can also define partial structures and interfaces.
You learned in Chapter 2 that there is a hidden code file behind the ThisDocument.vb file in Word solutions (and behind ThisWorkbook.vb, Sheet1.vb, Sheet2.vb, and Sheet3.vb in Excel solutions). These code files are partial classes. VSTO uses partial classes as a way to separate auto-generated code from the code that you write so that you can concentrate on your own code. Partial classes are also used in Windows Forms to store auto-generated code when you add controls to a Windows Form.
Figure 3.4 shows the partial class named MyForm, which contains the code that is generated whenever you add a control to the form. This code is stored in the MyForm.Designer.vb file; in contrast, the code you write to set properties or handle the events of the control should be written in MyForm.vb, as shown in Figure 3.5.
Figure 3.4 Partial class for a Windows Form named MyForm, where auto-generated code is stored
Figure 3.5 Partial class for a Windows Form named MyForm, where developer-written code is stored
Notice that the class definition for MyForm.Designer.vb is Partial Class MyForm. The class definition for MyForm.vb does not contain the Partial keyword. Instead, it is simply Public Class MyForm.
The Partial keyword is not needed for the main class definition; it is needed only for any additional class definitions that share the same class name. When you compile the code, Visual Basic automatically merges the code from the partial classes with the code for the main class.
Another way you might use partial classes is to divide a programming task between two developers. If each developer writes code in a separate class file, you can then add each class to the main project and Visual Studio will automatically merge the classes during the build process as if they were a single class file.
Generic classes are a new feature in the .NET Framework and are supported in Visual Basic 2005. A generic class is a single class that provides functionality for different data types, without the need to write a separate class definition for each data type. You can also define generic methods, structures, and interfaces.
A generic class uses type parameters as placeholders for the data types. The code example in Listing 3.10 shows the declaration of a generic class using t to represent the type parameter. You can specify more than one parameter by separating the parameters with commas. When you want to instantiate the class, you must specify the data type, rather than the type parameter, in the declaration, as shown in Listing 3.10.
Listing 3.10. Creating a Generic class
Public Class MySampleClass(Of t) ' Implementation code for the class goes here. End Class Sub CreateGenericClasses() Dim myStringClass As New mySampleClass(Of String) Dim myIntegerClass As New mySampleClass(Of Integer) End Sub
In the System.Collections.Generic namespace, the .NET Framework provides a number of generic collection classes that correspond to existing (nongeneric) collection classes. For example, you can use a Dictionary class to specify the data type for a key-value pair (rather than use a Hashtable), and a List is the generic class that corresponds to an ArrayList.
Like classes, interfaces define properties, methods, and events of an object. Unlike classes, interfaces do not provide any implementation, and you cannot create an instance of an interface. A class can implement one or more interfaces.
Any class that implements an interface must implement all the members of the interface as they are defined. An interface should not change after it has been deployed in your solution, because any such change could possibly break existing code.
You declare interfaces using the Interface statement. For example, the code in Listing 3.11 defines an interface that must be implemented with a method that takes an Integer argument and returns an Integer. When you implement this interface in a class, the data type for the arguments and the return value of the method must match those of the method defined (in this case, Integer). You can implement this interface within a class by using the Implements keyword, as shown in Listing 3.11.
Listing 3.11. Creating and implementing an interface
Public Interface ISampleInterface Function SampleFunction(ByVal Count As Integer) As Integer End Interface Public Class SampleClass Implements ISampleInterface Function SampleFunction(ByVal Count As Integer) As Integer _ Implements ISampleInterface.SampleFunction ' Add code to perform the function here. End Function End Class
Code modules in Visual Basic work in the same way as they do in VBA. A code module is a container for global methods and properties that can be used by other parts of your application. Unlike classes, you do not need to create a new instance of a module in order to call the methods.
Nor do you need to fully qualify your call unless the same method exists in multiple modules. To fully qualify the method name you would use moduleName.methodName().
Modules are a simple way to create code that can be accessed from anywhere in your application, but in general it is better to use classes and object-oriented techniques. In the next section you will learn about the benefits of object-oriented programming.
To be considered a true object-oriented language, a language should support the following features:
All these features were available in VBA except inheritance. This is one reason many people never considered VBA a true object-oriented programming language. This isn't to say, however, that Visual Basic 2005 is merely VBA plus inheritance. Many more capabilities and enhancements have been made to Visual Basic 2005.
In this section we look at these encapsulation, inheritance, and polymorphism requirements of object-oriented programming.
Encapsulation enables you to control the access to data within a class. For example, suppose your class has a number of methods that work on some data. Code that calls into the instantiated class (the object) need not know how a particular operation functions. To perform an action, the calling code need know only that the functionality exists and that it needs to call it. By not allowing direct external access to those methods and by hiding the logic used in the class, you are following the principle of encapsulation.
You can hide the implementation of your class by using access modifiers that prevent code outside the class from modifying data within the class or calling its methods. For example, you can use the Private keyword with a property or method that you don't want outside code to access. However, if you want to manipulate data from outside the class, you must provide public properties or methods. This was illustrated in the Sentence class you created earlier in this chapter. The Text property of the Sentence class had a Get property procedure and a Set property procedure that enabled you to write code to assign values to and retrieve values from the property. The actual data, however, was stored in a private member variable that was not directly accessible from outside the class.
The value of this feature becomes clearer if we add logic along with setting the internal value, such as checking the spelling of the sentence. The developer who sets the text property doesn't have to know how the Sentence object is checking the spelling, only that it does check the spelling when it sets the value.
Using inheritance, you can create a class that is based on an existing class, giving your new class all the behavior and functionality of the existing class. The class that you inherit from is known as the base class, and the class that is inheriting the functionality is known as the derived class. You can extend the functionality of the derived class by adding properties or methods that did not exist in the base class, or you can override inherited properties or methods so that they behave differently in the derived class.
Visual Basic 2005 supports inheritance, although it doesn't support multiple inheritance. A derived class can have only one base class.
Using inheritance, you can reuse existing code that performs most of the functionality you require. You modify only a portion of the code to meet your needs, instead of having to reinvent the wheel. Whenever you need functionality in your application, you should look at the .NET Framework class library to see whether the functionality exists or whether you can inherit the base functionality from one of the classes. For example, if you want to extend an existing Textbox control, you can create a new class that derives from the Windows Forms Textbox control, as shown in Listing 3.12.
Listing 3.12. Inheriting from an existing Windows Forms control
Public Class MyTextBox Inherits System.Windows.Forms.TextBox ' Add code to override existing TextBox functionality. End Class
Polymorphism is the ability to create identically named methods or properties within a number of derived classes that perform different functions. You can implement polymorphism by using interfaces or inheritance. For inheritance-based polymorphism, you override methods in a base class with new implementations of the methods in the derived class. For interface-based polymorphism, you implement an interface differently in multiple classes. You saw an example of this when you created multiple constructors for your Sentence class.