4.1. Properties

An important element of any class is its properties. These are the equivalent of global or public variables in code modules. However, a property has the added advantages that you can both validate a value and execute other code every time a value is assigned to the property. In addition, properties declared within class modules can be accessed from outside the current project. Properties help eliminate the clutter of global variables that plagued almost every large-scale VB3 application I've seen, and that made both the development and maintenance of VB3 applications a nightmare.

4.1.1. Implementing Properties

Properties allow users and other programmers (including yourself) to safely access data. In many ways, properties are simply variables that hold a particular value or object. But with careful planning and a professional approach, you can turn these simple variables into powerful tools.

Take, for example, a class that is acting as a wrapper for a collection object. You may have a read-only property within your class called Count that returns the number of records held within the collection. Your Count property would simply pass on the Count property of the collection object. However, you could write code within your class's Count property procedure that checks if the value of the collection object's Count property is zero and, if it's zero, calls a procedure that populates the collection. In this way, the user of your class could populate the collection automatically by returning the Count property, as the following snippet demonstrates:

Public Property Get Count() As Long
    If mcolAnyColl.Count = 0 Then
        Call PopulateCollection()
    End If
    Count = mcolAnyColl.Count
End Property

A major use of property procedures is to validate data. Without class modules and properties, validation is typically performed at the form level; for example, the Change or LostFocus event handler of a TextBox control determines if the data entered in the text box is acceptable. It often happens, though, that this value is referenced on many different forms, so that each reference has to be accompanied by the data validation code. If a programmer gets the validation code wrong, or simply ignores or forgets the validation altogether, you risk accepting invalid data into your database. If the validation rules change, you have to visit each form that contains a reference to the data item and change the validation code.

Now contrast this scenario with validation within a property procedure. The form can attempt to assign a nonvalidated value to the property, and the property procedure will validate the data and accept or reject it. The validation for this data item is thereby centralized. Any form can use the property, and you can be certain that only validated data is accepted into the database. Furthermore, if a change to the validation is required, you only have to change the validation logic in one place, thereby reducing the risk of error.

The following snippet demonstrates using a Property procedure to implement business rules to validate incoming data:

Public Property Let ClaimDate(dVal as Date)
   'business rule: a claim cannot be more than 10 days old
   If dVal < DateAdd("d",-10,Now) Then
      Err.Raise vbObjectError + 700001, "", _
                "Cannot be more than 10 days ago"
   Else
      mdClaimDate = dVal
   End If
End Property

A property can be of any intrinsic data type or any user-defined object. As of VB6, you also can create a property whose data type is a user-defined type, although the implementation of this is somewhat different than that for a normal property; for details, see Section 4.1.3 later in this chapter.

Properties can also be defined in Form modules, and can remove the need for global or public variables to pass state from one module to another in a project. For example, if you need to set a value for a form to use as it loads, the traditional method is to assign the value to a public variable, which is then accessible to the form. The problem with this method is that before long, a large project includes truckloads of global variables and keeping track of them is a nightmare. Instead, you should create a property in the form's code module; then, from outside the form, you can assign a value to this property, as the following code snippet shows:

Dim oFrm As frmEmpForm
Set oFrm = New frmEmpForm
oFrm.CurrentEmployeeCode = "0123"
oFrm.Show vbModal
Set oFrm = Nothing

You can create properties that return other objects, thereby building a hierarchy of objects. For example, consider an application that deals with employees. The individual employee's record could be held in a collection wrapper class called Employee, and the code to save and retrieve an employee's record that's common to all employees could be held in a class called Employees. You could then place a property procedure called Employee in the Employees class that returns an Employee class object, similar to the following:

Public Property Get Employee(sEmployeeCode As String) _
                            As Employee
   Set Employee = mcolEmployees.Item(sEmployeeCode)
End Property

You could then access a particular employee's record from the client application using the code:

TxtEmployeeName.Text = Employees.Employee("0123").Name

Having examined the vital role that property procedures can play in ActiveX object creation, let's look at the separate procedures you need to employ within your class to successfully implement a robust property.

4.1.2. The Anatomy of a Property

A properly defined read-and-write property consists of three components:

  • A Private member variable to hold the actual data

  • A Property Let procedure to validate and accept the incoming value, or a Property Set procedure to validate and accept an incoming object reference.

  • A Property Get procedure to pass the value of the private member variable to the calling program.

4.1.2.1. Private member variable

The Private member variable holds the property's actual value or object reference. By declaring the variable Private, you protect it from the outside world; from outside its module, it's accessible by the Property Let, Get, or Set procedures.

It's possible to create a property within a class module by simply dimensioning a Public variable. However, its value can be modified from any other module in your project by a simple assignment statement. This isn't recommended, since it fails to take advantage of the benefits associated with property procedures.


When creating a property for a user-defined type, the type declaration must be declared as Public. See Section 4.1.3 for details.


The name of this variable should differ from that of the property to which it's attached. This is usually achieved using standard VB naming conventions and prefixing the name with a lowercase "m" to denote a member variable. For example:

Private msForeName As String
Private miNumber   As Integer

4.1.2.2. The Property Let procedure

A Property Let procedure assigns a value to a property. You can perform all your data validation within the Property Let procedure before assigning the incoming value to your Private member variable. (For full details, see the entry for the Property Let statement in Chapter 7.) In its simplest form, a Property Let procedure looks something like this:

Public Property Let ForeName(sVal As String)
    msForeName = sVal
End Property

The data type of the property's argument must be the same as that of the private member variable to which its value is assigned.

A Property Let procedure doesn't return a value. Therefore, to reject a value within your data validation code, you should use the Err.Raise method to generate a trappable error in the client application.

A tricky issue is that of generating a warning, rather than an error. Let's say that part of your validation code checks the value of a data item and, if it's above a certain amount, warns the user that the amount appears high. You don't want to reject the value out of hand, because there could be a valid reason for the high value. Now, if you coded the warning as follows, you would never get past the warning if the value was above 10,000:

Public Property Let ClaimValue(dVal As Double)
   If dVal > 10000 Then
      Err.Raise vbObjectError + 40000, "", _
                "The Claim Value appears high"
   Else
      mdClaimValue = dVal
   End If
End Property

The way to get around this is to raise an event in the client. Events are covered later in the chapter, and we'll come back to this example in that section.

4.1.2.3. The Property Get procedure

The Property Get procedure is much like a function: just as you assign a value to the name of the function to define its return value, you assign the value of the Private member variable to the name of the Property Get procedure and thereby return the property value. (For full details, see the entry for the Property Get procedure in Chapter 7.)

In its simplest form, a Property Get procedure looks something like this:

Public Property Get ForeName() As String
   ForeName = msForeName
End Property

The data type returned by the Property Get procedure must be the same as that of the Private member variable.

If the data type of the property is an object reference, you must use the Set statement to return its value, like this:

Public Property Get Employee(sEmployeeCode As String) _
                    As Employee
   Set Employee = mcolEmployees.Item(sEmployeeCode)
End Property

4.1.2.4. Property scope

The keyword you employ to declare a property procedure determines where the property can be used:


Private

Restricts the visibility of the property to the class module within which the method is defined. This is pointless, since you achieve identical results with a private variable.


Friend

Restricts the visibility of the property to those modules contained within the same project as the property definition. Friend properties appear in the IntelliSense drop-down list for the class and are made available for statement completion in other modules within the same project.


Public

Allows the property to be called from within the class module, from other modules in the project, and from outside the project. Defining a property as Public adds the property declaration to the Type Library for the class. When a reference is made to the class from another project, Public properties appear in the IntelliSense drop-down list for the class and are made available for statement completion.

4.1.3. Implementing a User-Defined Type Property

Visual Basic 6 adds the user-defined type (UDT) to the list of data types a property can represent. However, its use isn't intuitive. Here are the steps needed to create a UDT property in VB6:

  1. Declare a Public user-defined type definition.

  2. Declare a Private member variable whose data type is that of the user-defined type.

  3. Declare a Public Property Get procedure whose data type is that of the user-defined type.

  4. The assignation within the Property Get procedure should be the Private member variable.

  5. Declare a Public Property Let procedure. The data type of the value parameter is that of the user-defined type.

In addition, client applications using the UDT property can do so only by using early binding. See Section 4.6 later in this chapter for information about early binding.

Here's a quick example:

Server code:

Public Type udtTestType
   EmployeeNo As Integer
   EmployeeName As String
End Type

Private mudtTestType As udtTestType

Public Property Get TestType() As udtTestType
    TestType = mudtTestType
End Property

Public Property Let TestType(udtVal As udtTestType)
    mudtTestType = udtVal
End Property

Client code:

Dim oServer As Server.ServerClass
Dim oRemUDT As Server.udtTestType

Set oServer = New Server.ServerClass

oRemUDT = oServer.TestType

oRemUDT.EmployeeName = "Tim"
oRemUDT.EmployeeNo = 1

oServer.TestType = oRemUDT 

Set oServer = Nothing

To use remote user-defined types in this manner, you need a computer with NT4 and NT Service Pack 4 or above.


4.1.4. Implementing a Read-Only Property

To define a property as read-only, you should implement the following:

  • A Private member variable to hold the actual data

  • A Property Get procedure to pass the value of the private member variable to the calling program

In this way, the "outside world" can't change the value of the property, because there is no Property Let procedure. You have complete control over the value of the property within the class.

4.1.5. Using Properties in the Client Application

Within a procedure, you shouldn't make more than one call to a particular property. Each time you call a property, there is overhead, which can result in a massive performance hit on your application. This code snippet shows how not to use a property:

Dim oEmps as Employees
Set oEmps = New Employees
If oEmps.EmployeeNo <> sExcludedEmpNo Then
   TxtEmpNo.Text = oEmps.EmployeeNo
   Call BuildSomeCombo(oEmps.EmployeeNo)
   MsgBox "Found Employee No" & oEmps.EmployeeNo
End If
Set oEmps = Nothing

In this simple example, there are four calls to the EmployeeNo property. That's four times the client application has to navigate to the Property Get procedure, and four times the Property Get procedure has to execute. You can optimize this code by accessing the property once and storing its value in a local variable, as the following code fragment illustrates:

Dim oEmps as Employees
Dim sEmpNo as String
Set oEmps = New Employees
sEmpsNo = oEmps.EmployeeNo
Set oEmps = Nothing

If sEmpNo <> sExcludedEmpNo Then
   TxtEmpNo.Text = sEmpNo
   Call BuildSomeCombo(sEmpNo)
   MsgBox "Found Employee No" & sEmpNo
End If

You may have noticed another important benefit we've managed to gain in the reworked code. The object reference is destroyed at a much earlier stage, which means that the object is used only for a short period of time. This is a major consideration as far as scalability is concerned when designing large client-server applications.

4.1.6. Using a Mass Assignation Function in Collection Classes

What's a mass assignation function? Let's say you have a collection class that contains 20 properties, which you populate by reading data from a database. You have a procedure that opens the database, creates a recordset, and then assigns the values from the recordset to each of the relevant properties in the class, something like this:

oClass.EmployeeNo = rsRecordset!EmpNo
oClass.FirstName = rsRecordset!FirstName
Etc...

Using this method, you are calling the Property Let procedure of each property. If there is validation code within the Property Let procedure, this must execute too, most likely on data that has been validated before being saved in the database. A more efficient method of population is to create a function within the collection class, like this:

Friend Function Initialize(sEmpNo as String, _
                sFirstName as String ...etc) As Boolean
msEmpNo = sEmpNo
msFirstName = sFirstName
...Etc...

This single function assigns all the values for the object in one go by assigning the values directly to the local variables, thus bypassing the Property Let procedures and the redundant validation code. You can therefore pass all the values to populate the object at once:

If oClass.Initialise( rsRecordset!EmpNo, _
                     rsRecordset!FirstName, _
                     etc...) Then

Of course, you should only use this method within a class module—never from outside—and you should only employ this against data you are certain has already been validated.

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

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