Classes

One of the major thrusts of Visual Basic .NET is its enhanced emphasis of object-oriented programming capabilities. Classes are the main type for building custom components . This section presents a brief overview of class design and processing issues; it serves as a prelude to two additional sections that demonstrate programming techniques for creating and using class properties and class methods . Chapter 4 extends this introduction to class programming by providing more in-depth coverage of techniques such as event processing and inheritance.

Overview

Classes are your tools for building custom components. A class is the model for an object. Consider a cookie class. A cookie eater can remove a cookie from its jar to make a cookie instance available for eating . In addition, a cookie eater can remove multiple cookies (instances) from the jar at the same time. The multiple instances for a class can expose properties with different values. For example, one cookie can have a hard-texture property while another cookie can have a soft-texture property. Cookies can have methods as well, such as start_eating and stop_eating . After launching the start_eating method for a cookie, you can invoke its stop_eating method any time before its percent_consumed property equals 100. When the percent_consumed property reaches 100, the cookie can raise an all_gone event. The cookie eater is the consumer of a cookie instance.

In our analogy, the cookie eater corresponds to a Windows application project or any other project that can make a reference to a class, such as the cookie class. If the cookie eater has an event handler for the all_gone event, the cookie eater can respond to the event (for example, by making a new cookie instance). When the cookie eater is finished, he or she can leave the room with the cookie jar (go out of scope) or put the cover back on the jar (assign Nothing to the cookie class). Eventually, the .NET garbage collector will remove the cookie jar instance from the kitchen counter (memory) when it determines that no more active references to the class exist.

In fact, you can have multiple classes that inherit from one another. Consider a base cookie class. It can have methods such as start_eating and stop_eating , a percent_consumed property, and an all_gone event. All cookies must have these properties. Different derived cookie classes will have the properties and methods for the base class as well as additional properties, methods, and events. A chocolate chip cookie class and a wafer cookie class can both derive from the base cookie class. This inheritance feature permits a chocolate chip cookie to possess all the properties, methods, and events of the base cookie class. The same goes for the wafer cookie class. However, the wafer class can have a flavor property with settings of vanilla , strawberry, and chocolate that is not available for the chocolate chip class. Likewise, the chocolate chip class might have a with_nuts property with settings of True or False that is not present in the wafer cookie class.

Creating and Using Class Properties

There are two main ways to implement properties with classes ”with public fields and with property procedures. Property procedures offer more flexibility, but they also require more programming. These two approaches for implementing properties in Visual Basic .NET parallel those for classic Visual Basic, but property procedures have minor syntax differences. In addition, Visual Basic .NET introduces the notion of a shared member , which can apply equally well to a property or a method. You reference shared members directly from a class instead of from a class instance. Rather than dwell on the differences in class processing techniques between classic Visual Basic and Visual Basic .NET, this presentation of property processing techniques explains how to implement properties and manipulate them with Visual Basic .NET.

The demonstrations for class properties rely on two projects. The first project, PropertiesDemoLibrary, is a Class Library project. This project contains three classes that implement properties differently. All three classes use the COM Interop feature to reference the ADODB COM object. The code for each class extracts values from a column in any table from the Northwind database and persists them within the class in a one-dimensional array. The properties exposed by each class are for the array with the data from the Access database. The three properties for each class include the length of the array, the first value in the array, and the last value in the array.

I created the second project from the Empty Project template. This second project, PropertiesDemoLibUser, consumes objects based on the classes in the first project. For this second project to interoperate with the first project, you must create a reference in the second project that points at the .dll file for the first project. (See the Consuming a Class Library Project section in Chapter 2 for detailed instructions on referencing a class library .dll file.) In a sense, you can think of the first project as the cookie jar and the second project as the cookie eater. The second project extracts an object from the first project by invoking the New method for a class, and it specifies the type of cookie, or object, that it wants to extract by specifying a table name and column number. You can use the second project to extract a cookie, or object, based on any of the tables in the Northwind database. For example, an object instance based on the first column in the Shippers table will have different properties for its length ( MyArrayLength ), first value ( MyArrayFirst ), and last value ( MyArrayLast ) than for an object based on the second column in the Customers table.

Class Listing

The listing for the PropertiesDemoLibrary appears next . The overall listing is lengthy, but it divides well into three parts ”one part for each class declaration within the class library. Each class exposes its properties differently. Therefore, the code for each class is presented separately. The MyArrayField Properties class implements its properties via fields with a Public access modifier. When using this approach to implement a property, your properties are always read/ write. That is, a client implementing an instance of the class can read the class property and write to it. In addition, you cannot put any code behind the property, such as validation code to make sure it meets certain requirements (for example, a birthday being on or after today).

The MyArrayField Properties class has a single procedure named New . This Sub procedure is the constructor for the class. The procedure takes two arguments, which designate the table name and column to extract from the Northwind database and store in a local array, ary_obj1 . The procedure uses the COM Interop feature with the ADODB library to extract the values from the Northwind database. The code specifies a connection for the Northwind database in its default location. The sample then proceeds to use the connection to open a recordset on the table designated by the first argument for the New method. Next the sample declares the ary_obj1 array. The array s upper index value is computed from the RecordCount property of the Recordset object. The code declares the array to have an Object type so that it can accommodate a column with any type from the Northwind database. After making the recordset and the array available, the constructor loops through the recordset rows and copies values from the designated table column to the ary_obj1 array. The constructor concludes by setting properties declared at the top of the procedure. These property value settings depend on the array populated by passing through the preceding loop.

 Public Class MyArrayFieldProperties Private My_int1 As Integer 'Declare properties as fields with 'Public access modifier. Public MyArrayLength As Integer Public MyArrayFirst As Object Public MyArrayLast As Object 'Class constructor Sub New(ByVal TblName As String, _ ByVal ColNumber As Integer) 'Declare and instantiate ADODB objects; 'add references through COM Interop to 'to class library project. Dim cnn1 As New ADODB.Connection() Dim rst1 As New ADODB.Recordset() 'Open an ADO Connection object. cnn1.Open("Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=C:\Program Files\" & _ "Microsoft Office\Office10\Samples\Northwind.mdb;", _ "Admin", "") 'Open a recordset on a table. With rst1 .ActiveConnection = cnn1 .Open(TblName, , _ ADODB.CursorTypeEnum.adOpenKeyset, _ ADODB.LockTypeEnum.adLockOptimistic) End With 'Declare the array with an upper bound. Dim ary_obj1(rst1.RecordCount - 1) As Object 'Loop through the ADO recordset and assign column 'values to the array. Do Until rst1.EOF ary_obj1(My_int1) = rst1(ColNumber).Value My_int1 += 1 rst1.MoveNext() Loop 'After initializing array, assign class properties. MyArrayLength = ary_obj1.GetLength(0) MyArrayFirst = ary_obj1(0) MyArrayLast = ary_obj1(ary_obj1.GetUpperBound(0)) End Sub End Class 

The second class, MyArrayPropertyProcedures , extends the preceding class by implementing the three properties with property procedures. Because some code is borrowed from the preceding class, the listing shows the new code in boldface. No field declarations appear at the beginning of this class because the class implements properties with procedures. However, it is necessary to declare the ary_obj1 array with a Private access modifier. The array was implicitly private in the preceding class definition because the code for that class defined the array within the New procedure. However, for this class, several procedures must have access to the array. Declaring the array at the top of the class makes the array available to all procedures within the class. Three property procedures for the length, first, and last property values appear after the close of the code for the New Sub procedure. The first value property, MyArrayFirst , is a read/write property. Although the MyArrayFirst property is for the first procedure, it actually appears second among the property procedures. The MyArrayLength and MyArrayLast properties are both read-only. As you can see, the only difference between these two types of properties is that the read-only properties omit the Set clause. This clause permits your application to copy a value into a property, and you can insert validation logic if you want. The Get clause returns a property value to an instance of the class.

 Public Class MyArrayPropertyProcedures  'Declare with Private access modifier for exclusive   'access from within objects based on the class.   Private ary_obj1() As Object  'Declare private Integer variable used in the class. Private My_int1 As Integer 'Class constructor Sub New(ByVal TblName As String, ByVal ColNumber As Integer) 'Declare and instantiate ADODB objects. Dim cnn1 As New ADODB.Connection() Dim rst1 As New ADODB.Recordset() 'Open an ADO Connection object. cnn1.Open("Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=C:\Program Files\" & _ "Microsoft Office\Office10\Samples\Northwind.mdb;", _ "Admin", "") 'Open a recordset on a table. With rst1 .ActiveConnection = cnn1 .Open(TblName, , _ ADODB.CursorTypeEnum.adOpenKeyset, _ ADODB.LockTypeEnum.adLockOptimistic) End With 'Declare the array with an upper bound. 'Dim ary_obj1(rst1.RecordCount - 1) As Object ReDim ary_obj1(rst1.RecordCount - 1) 'Loop through the ADO recordset and assign column 'values to the array. Do Until rst1.EOF ary_obj1(My_int1) = rst1(ColNumber).Value My_int1 += 1 rst1.MoveNext() Loop End Sub  'MyArrayLength property is read-only.   Public ReadOnly Property MyArrayLength() As Integer   Get   Return ary_obj1.GetLength(0)   End Get   End Property   'MyArrayFirst property is read/write.   Public Property MyArrayFirst() As Object   Get   Return ary_obj1(0)   End Get   Set(ByVal Value As Object)   ary_obj1(0) = Value   End Set   End Property   'MyArrayLast property is read-only.   Public ReadOnly Property MyArrayLast() As Object   Get   Return ary_obj1(ary_obj1.GetUpperBound(0))   End Get   End Property  End Class 

The third class in the PropertiesDemoLibrary project has the name MyArraySharedPropertyProcedures . Because the code for this class again borrows from the preceding two classes, the new code is highlighted in bold. This class demonstrates the use of the Shared modifier for a property. Shared modifiers enable the user of a class to reference the class member ”either a property or a method ”before instantiating an object based on the class.

As is common practice for classes based on variables , the MyArraySharedPropertyProcedures code declares the m_TblName and m_ColName fields with a Private modifier for use within the class. The TblName and ColName properties are shared outside the class. Users can specify these properties even before instantiating the object. Then all subsequent instances based on the class will use those property settings until the user application changes the shared property values. Shared property values go with the class rather than the specific class instances, so changing them for the class changes them for all instances of the class. Property values declared without the Shared modifier have specific values for each instance.

 Public Class MyArraySharedPropertyProcedures 'Declare private member of class used 'in computations to return read-only properties. Private ary_obj1() As Object  Private Shared m_TblName As String   Private Shared m_ColNumber As Integer  'Declare private Integer variable used in the class. Private My_int1 As Integer 'Class constructor Sub New() 'Declare and instantiate ADODB objects. Dim cnn1 As New ADODB.Connection() Dim rst1 As New ADODB.Recordset() 'Open an ADO Connection object. cnn1.Open("Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=C:\Program Files\" & _ "Microsoft Office\Office10\Samples\Northwind.mdb;", _ "Admin", "") 'Open a recordset on a table. With rst1 .ActiveConnection = cnn1 .Open(m_TblName, , _ ADODB.CursorTypeEnum.adOpenKeyset, _ ADODB.LockTypeEnum.adLockOptimistic) End With 'Declare the array with an upper bound. 'Dim ary_obj1(rst1.RecordCount - 1) As Object ReDim ary_obj1(rst1.RecordCount - 1) 'Loop through the ADO recordset and assign column 'values to the array. Do Until rst1.EOF ary_obj1(My_int1) = rst1(m_ColNumber).Value My_int1 += 1 rst1.MoveNext() Loop End Sub  'TblName property is Shared.   Shared Property TblName() As String   Get   Return m_TblName   End Get   Set(ByVal Value As String)   m_TblName = Value   End Set   End Property   'ColName property is Shared.   Shared Property ColNumber() As Integer   Get   Return m_ColNumber   End Get   Set(ByVal Value As Integer)   m_ColNumber = Value   End Set   End Property  'MyArrayLength property is read-only. Public ReadOnly Property MyArrayLength() As Integer Get Return ary_obj1.GetLength(0) End Get End Property 'MyArrayFirst property is read/write. Public Property MyArrayFirst() As Object Get Return ary_obj1(0) End Get Set(ByVal Value As Object) ary_obj1(0) = Value End Set End Property 'MyArrayLength property is read-only. Public ReadOnly Property MyArrayLast() As Object Get Return ary_obj1(ary_obj1.GetUpperBound(0)) End Get End Property End Class 

Demonstrations for Invoking Classes

A single client project can demonstrate various features of the preceding three classes. The PropertiesDemoLibUser project includes a main procedure that can call any of the other four procedures in the project. Run the four successive demonstrations one at a time by removing the comment marker from a procedure name in the main procedure. Three Imports statements precede any of the procedures. These statements create shortcuts for each of the three classes in the PropertiesDemoLibrary project.

The CreateFieldPropertyInstance procedure invokes the MyArrayFieldProperties class within the PropertiesDemoLibrary project. First, the procedure instantiates an instance of the class. Then it displays the length ( MyArrayLength ), first ( MyArrayFirst ), and last ( MyArrayLast ) property values. These values derive from the constructor within the class for the table name and column number passed to the constructor when instantiating an object. The column number is zero-based , meaning that a column number of 1 denotes the second column. Next the procedure updates the first value property. This changes the value in the object instance; it does not update the record source, which is Shippers in the sample, for the underlying Northwind database. To demonstrate the change to the property value, the procedure presents two message boxes ”one containing the property value before the change, and a second containing the property value after the change. Figure 3-9 shows the three message boxes generated by the CreateFieldPropertyInstance procedure. If you rerun the procedure, the same three message boxes appear. This is because the change indicated in the third message box is made only to the in-memory object instance, not the underlying data source in the Northwind database file.


Figure 3-9: After instantiating an object, you can access property values of the object ”for example, for display in a message box. You can also change property values from their initial values (Speedy Express) to new values (anything).

The message boxes generated by the second and third procedure calls in the main procedure are the same as the ones appearing in Figure 3-9. In spite of this similarity in the output, some important differences exist in the behavior of the classes and how you invoke them. For example, all three properties for the object generated by the call to the CreateFieldPropertyInstance procedure are updatable. This is because the CreateFieldPropertyInstance procedure generates a property based on the MyArrayFieldProperties class, and this class implements properties with public fields. However, only the first value property is updatable for objects based on the MyArrayPropertyProcedures class. The second procedure call in the main procedure, CreateProcedurePropertyInstance , creates an object based on the MyArrayPropertyProcedures class, which uses property procedures to provide read/write access to only the first value property. The CreateProcedurePropertyInstance includes a commented attempt to update the last value property. If you remove the comment marker ('), Visual Studio .NET detects that you are attempting to edit a read-only property and generates a compilation error.

The CreateSharedProcedurePropertyInstance procedure illustrates the syntax for using shared properties. Notice that you specify shared properties relative to the name of the underlying class for an object instead of the name for an object instance. In addition, you can designate a value for a shared property value before you instantiate an object. Furthermore, the property values that you designate before instantiating an object (namely, the Shippers table and its second column) can help to specify the object that a class creates.

The primary purpose of the ProcessTwoObjectInstances procedure is to demonstrate issues related to opening two instances of a class at the same time. The initial code block starts by defining the TblName and ColName properties for the class. Then the code invokes the New method for the class to create MyInstance1 . Creating the second instance ( MyInstance2 ) is more direct because you do not need to specify the TblName and ColName shared class property values. After creating two instances of the MyArraySharedPropertyProcedures class, the ProcessTwoObjectInstances procedure displays in messages the first value property for each instance before and after editing the property. (See Figure 3-10.) The first message box shows the initial value for the property value in each instance. The second message box shows the property values for each instance after editing. Notice that instances preserve their distinct property assignments because we are dealing with two distinct object instances. As noted early in the chapter in the Value Type Objects vs. Reference Type Objects section, if two variables point at the same object instance, you cannot control independent values for each variable.


Figure 3-10: When you have multiple instances of a single object class, you can control the property value of individual instances independently of one another.
 Imports FieldProps = _ PropertiesDemoLibrary.MyArrayFieldProperties Imports ProcedureProps = _ PropertiesDemoLibrary.MyArrayPropertyProcedures Imports SharedProcedureProps = _ PropertiesDemoLibrary.MyArraySharedPropertyProcedures Module Module1 Sub main() 'CreateFieldPropertyInstance("Shippers", 1) 'CreateProcedurePropertyInstance("Shippers", 1) 'CreateSharedProcedurePropertyInstance("Shippers", 1) 'ProcessTwoObjectInstances("Shippers", 1) End Sub Sub CreateFieldPropertyInstance(ByVal InstanceTable As String, _ ByVal InstanceColNumber As Integer) 'Instantiate an object. Dim MyInstance As New FieldProps(InstanceTable, _ InstanceColNumber) 'Display properties for object. Dim str1 As String str1 = _ "MyInstance's length = " & _ MyInstance.MyArrayLength.ToString & vbCr & _ "MyInstance's first value = " & _ MyInstance.MyArrayFirst.ToString & vbCr & _ "MyInstance's last value = " & _ MyInstance.MyArrayLast.ToString MsgBox(str1) 'Display an object property before and after altering it. MsgBox(MyInstance.MyArrayFirst) MyInstance.MyArrayFirst = "anything" MsgBox(MyInstance.MyArrayFirst) End Sub Sub CreateProcedurePropertyInstance(ByVal InstanceTable As String, _ ByVal InstanceColNumber As Integer) 'Instantiate an object. Dim MyInstance As New ProcedureProps(InstanceTable, _ InstanceColNumber) 'Display properties for object. Dim str1 As String str1 = _ "MyInstance's length = " & _ MyInstance.MyArrayLength.ToString & vbCr & _ "MyInstance's first value = " & _ MyInstance.MyArrayFirst.ToString & vbCr & _ "MyInstance's last value = " & _ MyInstance.MyArrayLast.ToString MsgBox(str1) 'Display an object property before and after altering it. MsgBox("Before - MyArrayFirst property = " & _ MyInstance.MyArrayFirst.ToString) MyInstance.MyArrayFirst = "anything" MsgBox("After - MyArrayFirst property = " & _ MyInstance.MyArrayFirst.ToString) 'Removing comment marker generates a compile error 'because MyArrayLast is read-only. 'MyInstance.MyArrayLast = "anything" End Sub Sub CreateSharedProcedurePropertyInstance(ByVal InstanceTable _ As String, _ ByVal InstanceColNumber As Integer) 'Instantiate an object. SharedProcedureProps.TblName = InstanceTable SharedProcedureProps.ColNumber = InstanceColNumber Dim MyInstance As New SharedProcedureProps() 'Display properties for object. Dim str1 As String str1 = _ "MyInstance's length = " & _ MyInstance.MyArrayLength.ToString & vbCr & _ "MyInstance's first value = " & _ MyInstance.MyArrayFirst.ToString & vbCr & _ "MyInstance's last value = " & _ MyInstance.MyArrayLast.ToString MsgBox(str1) 'Display an object property before and after altering it. MsgBox("Before - MyArrayFirst property = " & _ MyInstance.MyArrayFirst.ToString) MyInstance.MyArrayFirst = "anything" MsgBox("After - MyArrayFirst property = " & _ MyInstance.MyArrayFirst.ToString) 'Removing comment marker generates a compile error 'because MyArrayLast is read-only. 'MyInstance.MyArrayLast = "anything" End Sub Sub ProcessTwoObjectInstances(ByVal InstanceTable As String, _ ByVal InstanceColNumber As Integer) 'Instantiate a first object instance. SharedProcedureProps.TblName = InstanceTable SharedProcedureProps.ColNumber = InstanceColNumber Dim MyInstance1 As New SharedProcedureProps() 'Instantiate a second object instance. Dim MyInstance2 As New SharedProcedureProps() 'Display MyArrayFirst property from both instances 'before editing. Dim str1 = "Before" & vbCr & _ "MyArrayFirst property for MyInstance1 = " & _ MyInstance1.MyArrayFirst.ToString & vbCr & _ "MyArrayFirst property for MyInstance2 = " & _ MyInstance2.MyArrayFirst.ToString MsgBox(str1) 'Edit MyArrayFirst property differently in 'each instance. MyInstance1.MyArrayFirst = "anything" MyInstance2.MyArrayFirst = "anything else" 'Display MyArrayFirst property from both instances 'before editing. Dim str2 = "After" & vbCr & _ "MyArrayFirst property for MyInstance1 = " & _ MyInstance1.MyArrayFirst.ToString & vbCr & _ "MyArrayFirst property for MyInstance2 = " & _ MyInstance2.MyArrayFirst.ToString MsgBox(str2) End Sub End Module 

Creating and Using Methods

Two main ways to program class methods exist ”by using Sub procedures or by using Function procedures. Methods differ from properties in that methods perform a task while properties either return or save a property setting for the class. Actually, this distinction between methods and properties is only partially true because you can perform tasks inside property procedures. However, the task that a property procedure completes typically relates to the property setting, such as ensuring a new property value is in a proper range before assigning the value to the property.

Both Sub and Function procedures can perform tasks. As indicated earlier, these procedures typically have a much wider scope than property procedures. For example, you can make a connection to an Access database in a Sub procedure or return the result of a computation based on a subset of the rows in an Access table with a Function procedure. The New method from the property samples presented earlier in the section instantiated Connection and Recordset objects to help populate an array that, in turn , served as the basis for the properties in a custom class. A Sub procedure implements this method. In addition, you can use the Shared keyword to modify your declaration of Sub and Function procedures. As with properties, a shared method is accessible without instantiating an instance of the object. In the discussion in Chapter 2 of the Show method for the MessageBox class, you learned that you can invoke the method without instantiating an object based on the MessageBox class. This is because the Show method is a shared method. You can define shared methods for custom classes that you can use without having to instantiate an object based on the class.

This section relies on two projects. The first project was derived from a Class Library project template. This project, MethodsDemoLibrary, contains a single class that demonstrates how to implement methods with both Sub and Function procedures. The second project, MethodsDemoLibUser, was created with an Empty Project template. This project demonstrates the syntax for invoking custom methods implemented with either Sub or Function procedures and shows the differences in invoking a shared method.

Class Listing

The MethodsDemoLibrary project contains a reference to the ADODB library. Pick the appropriate library version for your requirements; my sample selected the 2.5 version because so many Access 2002 applications use this version. The project defines a single class named MyArraySubAndFunctionMethods . This class is an adaptation of the MyArrayPropertyProcedures class from the PropertiesDemoLibrary project reviewed earlier in the section. The fresh code for this application appears in bold. Showing the whole class definition makes it easy to see how the application fits together. The adaptation drops the MyArrayLength property but retains the MyArrayFirst and MyArrayLast properties. One of the retained properties is applied in the review of method coding techniques, and the potential applicability of the other retained property will be obvious. The reason for the sample is the addition of two procedures. These procedures demonstrate the syntax for programming methods based on Sub and Function procedures. Both of the new procedures illustrate different techniques for putting an Access database to use with Visual Basic .NET.

The Sub procedure implements the ChangedShippersName method. The procedure implementing this method makes a connection to the Northwind database. Then the procedure uses the connection to invoke a Jet SQL UPDATE statement for the Shippers table. The ChangedShippersName procedure takes two arguments. One argument is a string value for the new CompanyName column value. The second argument is a ShipperID column value for the row in the Shippers table to update. The procedure constructs the UPDATE statement from its two arguments. Because the ShipperID column is a primary key, a value for this column uniquely identifies a row in the Shippers table for which to modify the CompanyName column value. It normally is not a good idea to implement user interface code within a class method. This is because you do not want to pause an application for a user who is away from his or her machine when a prompt occurs or whose incorrect response generates a runtime error. However, in this case, we want the user to confirm the edit before implementing it. The sample uses a MessageBox function that shows the UPDATE statement and asks whether the user is sure about making the change. If the user replies Yes, the procedure invokes the Connection object s Execute method for the SQL string ( str1 ) composed earlier in the procedure.

Use a Function procedure instead of a Sub procedure when your method needs to return a value type or object reference. The TotalOrder procedure computes the extended price for all the line items in an order. The procedure takes a single parameter, OrderID , to identify the rows to process in the Order Details table from the Northwind database. The Function procedure returns a Decimal value with the total extended price for the order. The procedure starts by making a connection to the Northwind database. Then the procedure creates a Jet SQL statement to select the rows from the Order Details table that match the OrderID value passed as an argument. This SQL string becomes an argument for a recordset s Open statement, which designs the recordset to contain the line items in the Order Details table for the designated OrderID value. A Do loop passes through the rows in the recordset and accumulates the extended price for each line item. When the loop relinquishes control, the OrderTotal memory variable contains the total extended price for the order designated by the OrderID argument. The Return statement at the close of the TotalOrder procedure passes this memory variable s value back to the calling procedure.

To keep the focus on the TotalOrder procedure s logic and its role in returning a value, I did not comment on the fact that the following listing actually contains two Function statements. You should use one of these Function declaration statements, but not both. I commented the basic Function statement and retained the Function statement with the Shared keyword because the MethodsDemoLibUser project references the Shared Function statement. As mentioned, using the Shared keyword before a procedure allows you to invoke a method even if you have no object based on the class. If you know that an application will always use a method with an object instance, you can specify a standard Function declaration statement. If it is necessary to invoke a method some or all of the time without an object instance, you must use the Shared keyword with your Function declaration statement.

 Public Class MyArraySubAndFunctionMethods 'Declare with Private access modifier for exclusive 'access from within objects based on the class. Private ary_obj1() As Object 'Declare private Integer variable used in the class. Private My_int1 As Integer 'Class constructor Sub New(ByVal TblName As String, ByVal ColNumber As Integer) 'Declare and instantiate ADODB objects. Dim cnn1 As New ADODB.Connection() Dim rst1 As New ADODB.Recordset() 'Open an ADO Connection object. cnn1.Open("Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=C:\Program Files\" & _ "Microsoft Office\Office10\Samples\Northwind.mdb;", _ "Admin", "") 'Open a recordset on a table. With rst1 .ActiveConnection = cnn1 .Open(TblName, , _ ADODB.CursorTypeEnum.adOpenKeyset, _ ADODB.LockTypeEnum.adLockOptimistic) End With 'Declare the array with an upper bound. 'Dim ary_obj1(rst1.RecordCount - 1) As Object ReDim ary_obj1(rst1.RecordCount - 1) 'Loop through the ADO recordset and assign column 'values to the array. Do Until rst1.EOF ary_obj1(My_int1) = rst1(ColNumber).Value My_int1 += 1 rst1.MoveNext() Loop End Sub 'MyArrayLength property is read-only. Public ReadOnly Property MyArrayLength() As Integer Get Return ary_obj1.GetLength(0) End Get End Property 'MyArrayFirst property is read/write. Public Property MyArrayFirst() As Object Get Return ary_obj1(0) End Get Set(ByVal Value As Object) ary_obj1(0) = Value End Set End Property 'MyArrayLast property is read-only. Public ReadOnly Property MyArrayLast() As Object Get Return ary_obj1(ary_obj1.GetUpperBound(0)) End Get End Property  Sub ChangeShippersName(ByVal NewName As String, _   ByVal TargetID As Integer)   'Declare and instantiate ADODB objects.   Dim cnn1 As New ADODB.Connection()   'Open an ADO Connection object.   cnn1.Open("Provider=Microsoft.Jet.OLEDB.4.0;"  &  _   "Data Source=C:\Program Files\"  &  _   "Microsoft Office\Office10\Samples\Northwind.mdb;", _   "Admin", "")   'Set up string for update.   Dim str1 As String = _   "UPDATE Shippers SET CompanyName = '"  &  NewName  &  _   "' WHERE ShipperID="  &  TargetID.ToString   'Set up string for prompt.   Dim str2 As String = _   "Are you sure that you want to execute this?"  &  _   vbCr  &  str1   'Get confirmation before performing update.   If MsgBoxResult.Yes = MsgBox(str2, MsgBoxStyle.YesNo) Then   cnn1.Execute(str1)   End If   End Sub   Shared Function TotalOrder(ByVal OrderID As Integer) As Decimal   'Function TotalOrder(ByVal OrderID As Integer) As Decimal   'Declare and instantiate ADODB objects.   Dim cnn1 As New ADODB.Connection()   Dim rst1 As New ADODB.Recordset()   'Open an ADO Connection object.   cnn1.Open("Provider=Microsoft.Jet.OLEDB.4.0;"  &  _   "Data Source=C:\Program Files\"  &  _   "Microsoft Office\Office10\Samples\Northwind.mdb;", _   "Admin", "")   'Open recordset for rows from Order Details,   'matching OrderID.   Dim str1 As String = _   "SELECT * FROM [Order Details] "  &  _   "WHERE OrderID ="  &  OrderID.ToString   'Open a recordset on a table.   With rst1   .ActiveConnection = cnn1   .Open(str1, , _   ADODB.CursorTypeEnum.adOpenForwardOnly, _   ADODB.LockTypeEnum.adLockReadOnly)   End With   'Loop through the ADO recordset and compute   'the total for the designated OrderID.   Dim Quantity As Integer   Dim UnitPrice As Decimal   Dim Discount As Decimal   Dim OrderTotal As Decimal   Do Until rst1.EOF   Quantity = CInt(rst1("Quantity").Value)   UnitPrice = CDec(rst1("UnitPrice").Value)   Discount = CDec(rst1("Discount").Value)   OrderTotal = _   Quantity * UnitPrice * (1 - Discount) + OrderTotal   rst1.MoveNext()   Loop   'Return OrderTotal value from TotalOrder method.   Return OrderTotal   End Function  End Class 

Demonstrations for Invoking Methods

The listing for the MethodsDemoLibUser project appears next. This project contains a main procedure with calls to three Sub procedures. The listing shows all the Sub procedure calls commented. I tested them one at a time by removing the comment marker for a Sub procedure call. Before running the samples in the listing, make sure that the MethodsDemoLibUser project has a reference pointing at the MethodsDemoLibrary project. The Imports statement is included at the top of the listing as a reminder of the need for the reference. If the MethodsDemoLibUser project had references to two different projects containing classes named MyArraySubAndFunctionMethods , the Imports statement could give you a shortcut name for each, eliminating the possibility of conflicts between the namespaces of each referenced project.

The call to the AddxToLastShipperAndRestore procedure performs two main tasks. First, the procedure appends an x to the CompanyName column value for the last row in the Shippers table. Second, the procedure restores the row to its initial value. Typically, you will want to run just the first part of a sample like this. The second part has the virtue of restoring your Shippers table to its initial state. A series of message boxes updates you on what's happening between the occurrence of the first task and the second one.

The AddxToLastShipperAndRestore procedure s first task starts by invoking the New method for the MyArraySubAndFunctionMethods class to create a fresh object based on the class. The object refers to the Shippers table and returns the CompanyName column, which has an index value of 1. The MyArrayLast property preserves the initial value in str1 , which the procedure also uses as a basis for computing the new company name (namely, str1 & "x" ). The str2 string variable preserves this value. Next the procedure presents in a message box the initial CompanyName column value for the last row in the Shippers table. Then the procedure invokes the ChangeShippersName method for the object instance ( MyInstance ) created at the beginning of the procedure. By passing str2 and 3 as arguments, the procedure directs the method to change the CompanyName column value to the value represented by the str2 string in the table s row with a ShipperID value of 3.

Note  

The ChangeShippersName method can update the underlying recordset value for the MyArrayLast property. It does not matter that the property is read-only because the method changes the source for the property rather than the actual property value.

After the method completes the update, the CompanyName column value in the Shippers table will have a different value in its last row than the MyArrayLast property for the MyInstance object. To confirm the update by showing the new value in a message box, the sample needs a new instance of the MyArraySubAndFunctionMethods class ( MyInstance2 ). However, the Visual Basic .NET code runs ahead of the Access update cycle for making the update available. (Access makes the update, but Visual Basic .NET can check for the changed value before it is available.) Therefore, the procedure introduces a 1-second pause. The code implements the pause with the DateAdd function and a Do loop. (In Chapter 4, you will see how to pause an application with either of two built-in timer classes.) After the pause, the first task concludes by displaying a message box with the value of the MyArrayLast property for the second object instance ( MyInstance2 ).

The second task uses the same kind of logic as in the first task to change the CompanyName column value for the row in the Shippers table with a ShipperID value of 3. Because the sample saved the initial value of the CompanyName column value in the str1 memory variable, the application uses that as one of the arguments for the ChangeShippersName method. To view the restored value, the code implementing the procedure s second task must resort to the same techniques for showing the initial change to the CompanyName column value ”namely, waiting a second and creating a new object instance based on the MyArraySubAndFunctionMethods class. Figure 3-11 presents the series of message boxes that the AddxToLastShipperAndRestore procedure presents. In the figure, you can see the message boxes directly programmed by the procedure as well as those indirectly invoked by calls to the ChangeShippersName method.

click to expand
Figure 3-11: The sequence of message boxes generated by running AddxToLastShipperAndRestore procedure

The ComputeOrderTotal procedure computes the total extended price across all line items for any OrderID value designated in its calling routine. The main routine listing that follows designates an OrderID value of 10248, but you can specify any valid OrderID number from the Northwind database. OrderID values appear in both the Orders and Order Details tables within the database.

The ComputeOrderTotal procedure starts by instantiating a new object instance based on the MyArraySubAndFunctionMethods class. The procedure specifies the Shippers table to populate the private array, ary_obj1 , used by objects based on the class, but the Shippers table is immaterial to the TotalOrder method. That s because the code for the TotalOrder method always creates a new connection and recordset based on a subset of the rows from the Order Details table in the Northwind database. The rows comprising the subset are the set whose OrderID values match the value passed when invoking the TotalOrder method. The method returns a Decimal value representing a currency. The procedure displays the currency with a message box. The currency appears with the local Windows currency by using the Format function. The MsgBox function designates a title for its message that specifies that the value showing in the message box is the total for whatever OrderID value is passed to the ComputeOrderTotal procedure.

The ComputeWithSharedTotalOrder procedure highlights the syntax for using a shared method. You can contrast this procedure with the ComputeOrderTotal procedure to reinforce your understanding of how to use standard methods from shared methods. You do not need to instantiate an object for the MyArraySubAndFunctionMethods class before invoking the shared method. In fact, you specify the method name relative to the class name instead of the name for an object instance. After the method returns a value, you can process the return the same way that you process a return value from any method ” regardless of whether the method is shared.

 Imports MyArraySubAndFunctionMethods = _ MethodsDemoLibrary.MyArraySubAndFunctionMethods Module Module1 Sub main() 'AddxToLastShipperAndRestore() 'ComputeOrderTotal(10248) 'ComputeWithSharedTotalOrder(10248) End Sub Sub AddxToLastShipperAndRestore() 'Instantiate object. Dim MyInstance As New _ MyArraySubAndFunctionMethods("Shippers", 1) 'Persist original last value property and 'prepare edited value. Dim str1 = MyInstance.MyArrayLast Dim str2 = str1 & "x" 'Display last shipper's name before edit, 'then invoke ChangeShippersName. MsgBox("Before - Last value: " & _ MyInstance.MyArrayLast) MyInstance.ChangeShippersName(str2, 3) 'Introduce a short pause (1 second) without a timer; 'allows time for automatic refresh to show edit. Dim ASecondLater = _ DateAdd(DateInterval.Second, 1, Now()) Do Until Now() > ASecondLater Loop 'Create a new instance so that you can show the change. Dim MyInstance2 As New _ MyArraySubAndFunctionMethods("Shippers", 1) MsgBox("After - Last value: " & MyInstance2.MyArrayLast) 'Restore original value. MyInstance.ChangeShippersName(str1, 3) 'Introduce a short pause without a timer. ASecondLater = DateAdd(DateInterval.Second, 1, Now()) Do Until Now() > ASecondLater Loop 'Create a new instance so that you can show the change. Dim MyInstance3 As New _ MyArraySubAndFunctionMethods("Shippers", 1) MsgBox("Restored - Last value: " & MyInstance3.MyArrayLast) End Sub Sub ComputeOrderTotal(ByVal OrderID As Integer) 'Instantiate object. Dim MyInstance As New MyArraySubAndFunctionMethods("Shippers", 1) 'Compute with TotalOrder method for instance. Dim OrderTotal As Decimal = MyInstance.TotalOrder(OrderID) 'Display results formatted as currency. MsgBox(Format(OrderTotal, "C"), _ MsgBoxStyle.Information, _ "Total for Order " & OrderID.ToString) End Sub Sub ComputeWithSharedTotalOrder(ByVal OrderID As Integer) 'Compute with TotalOrder method without an instance. Dim OrderTotal As Decimal = _ MyArraySubAndFunctionMethods.TotalOrder(OrderID) 'Display results formatted as currency. MsgBox(Format(OrderTotal, "C"), _ MsgBoxStyle.Information, _ "Total for Order " & OrderID.ToString) End Sub End Module 
 


Programming Microsoft Visual Basic. NET for Microsoft Access Databases
Programming Microsoft Visual Basic .NET for Microsoft Access Databases (Pro Developer)
ISBN: 0735618194
EAN: 2147483647
Year: 2006
Pages: 111
Authors: Rick Dobson

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net