Chapter 12. Advanced VBA Techniques

<feature><title>In This Chapter</title> </feature>

Why This Chapter Is Important

The Visual Basic for Applications (VBA) language is extremely rich and comprehensive. VBA is covered throughout this book because it applies to different topics, but this chapter focuses on some advanced application development techniques. These topics include user-defined types, arrays, advanced function techniques, and VBA compilation options. The mastering of these topics helps to ensure your success as a VBA programmer.

What Are User-Defined Types, and Why Would You Use Them?

A user-defined type, known as a struct or record, allows you to create a variable containing several pieces of information. User-defined types are often used to hold information from one or more records in memory. They can also hold related information that would otherwise be stored in several unrelated variables. Because each element of a user-defined type can be instructed to hold a particular type of data, each element in the type can be defined to correspond to the type of data stored in a specific field of a table. A user-defined type might look like this:

Public Type TimeCardInfo
   TimeCardDetailID As Long
   TimeCardID As Long
   DateWorked As Date
   ProjectID As Long
   WorkDescription As String * 255
   BillableHours As Double
   BillingRate As Currency
   WorkCodeID As Long
End Type

Notice that the type of data stored in each element has been explicitly declared. The element containing the string WorkDescription has been declared with a length of 255. User-defined types make code cleaner by storing related data as a unit. A user-defined type exists only in memory and is, therefore, temporary. It’s excellent for information that needs to be temporarily tracked at runtime. Because it’s in memory, it can be quickly and efficiently read from and written to.

Note

The code snippets shown in the previous example are located in the basDataHandling module.

Declaring a User-Defined Type

You declare a user-defined type by using a Type statement that must be placed in the module’s Declarations section. Types can be declared as Public or Private within a standard module. Types can be used, but can’t be declared in Form or Report modules.

Creating a Type Variable

A Type variable is an instance of the type in memory; it must be declared before you can use the type. To declare a Type variable, create a Local, Private, Module-Level, or Public variable based on the type. Depending on where you place this declaration and how you declare it (using keywords Dim, Private, or Public), you determine its scope. The same rules for any other kind of variable apply to Type variables. The Dim statement in the code that follows creates a variable called mtypTimeCardData. If you place this Dim statement in the module’s General section, it’s visible to all routines in that module (notice the m, indicating that it is declared at the module level). If you place it in a subroutine or function, it’s local to that particular routine:

Dim mtypTimeCardData As TimeCardInfo

Storing Information from a Record in a Form into a Type

After a Type variable has been declared, you can store data into each of its elements. The following code in the frmTimeCardHours form stores information from the form into a Type variable called mtypTimeCardData. The Type variable is declared as a Private variable in the General Declarations section of the form. The Type structure is declared in basDataHandling.

Private Sub cmdWriteToType_Click()

    'Retrieve control values and place them in the type structure
    mtypTimeCardData.TimeCardDetailID = Me.txtTimeCardDetailID
    mtypTimeCardData.TimeCardID = Me.txtTimeCardID
    mtypTimeCardData.DateWorked = Me.txtDateWorked
    mtypTimeCardData.ProjectID = Me.cboProjectID
    mtypTimeCardData.WorkDescription = Me.txtWorkDescription
    mtypTimeCardData.BillableHours = Me.txtBillableHours
    mtypTimeCardData.BillingRate = Me.txtBillingRate
    mtypTimeCardData.WorkCodeID = Me.cboWorkCodeID
End Sub

The code for this chapter can be found in the CHAP12EX.MDB database on the book’s CD-ROM. The advantage of this code is that, rather than creating eight variables to store these eight pieces of related information, it creates one variable with eight elements. This method keeps things nice and neat.

Storing Information from a Record in a Form into a Type

Retrieving Information from the Elements of a Type

To retrieve information from your Type variable, simply refer to its name, followed by a period, and then the name of the element. The following code displays a message box containing all the time card–hour information:

Private Sub cmdDisplayFromType_Click()
    'Retrieve information from the type structure
    MsgBox "Timecard Detail ID Is " & mtypTimeCardData.TimeCardDetailID &
Chr(13) & _
        "Timecard ID Is " & mtypTimeCardData.TimeCardID & Chr(13) & _
        "Date Worked Is " & mtypTimeCardData.DateWorked & Chr(13) & _
        "Project ID Is " & mtypTimeCardData.ProjectID & Chr(13) & _
        "Work Description Is " & Trim(mtypTimeCardData.WorkDescription) &
Chr(13) & _
        "Billable Hours Is " & mtypTimeCardData.BillableHours & Chr(13) & _
        "Billing Rate Is " & mtypTimeCardData.BillingRate & Chr(13) & _
        "Workcode ID Is " & mtypTimeCardData.WorkCodeID
End Sub

Note

In Chapter 16, “Error Handling: Preparing for the Inevitable,” an exercise shows a user-defined type used to hold pertinent error information. The example then replaces the user-defined type with properties of a custom error class. Although user-defined types are still useful and are, in fact, necessary for many Windows API function calls, custom class modules have replaced much of their functionality.

Working with Constants

A constant is a meaningful name given to a meaningless number or string. Constants can be used only for values that don’t change at runtime. A tax rate or commission rate, for example, might be constant throughout your application. There are three types of constants in Access:

  • Symbolic

  • Intrinsic

  • System-defined

Symbolic constants, created by using the Const keyword, improve the readability of your code and make code maintenance easier. Rather than referring to the number .0875 every time you want to refer to the tax rate, you can refer to the constant MTAXRATE. If the tax rate changes and you need to modify the value in your code, you’ll make the change in only one place. Furthermore, unlike the number .0875, the name MTAXRATE is self-documenting.

Intrinsic constants are built in to Microsoft Access: they are part of the language itself. As an Access programmer, you can use constants supplied by Microsoft Access, Visual Basic, and ActiveX Data Objects (ADO). You can also use constants provided by any object libraries you’re using in your application.

There are only three system-defined constants available to all applications on your computer: True, False, and Null.

Defining Your Own Constants

As mentioned, a symbolic constant is declared by using the Const keyword. A constant can be declared in a subroutine or function or in the General section of a Form, Report, or Class module. Unlike in previous versions of Access, constants can be strong-typed in Access 97, Access 2000, and Access 2002. There are several naming conventions for constants. One of them is to use a suitable scoping prefix, the letter c to indicate that you’re working with a constant rather than a variable, and then the appropriate tag for the data type. The declaration and use of a Private constant would look like this:

Private Const mccurTaxRate As Currency = .0875

The naming convention that I prefer is the use of a scoping prefix and typing the name of the constant in all uppercase. The example given previously is changed to appear as follows:

Private Const MTAXRATE as Currency = .0875

This code, when placed in a module’s Declarations section, creates a Private constant called MTAXRATE and sets it equal to .0875. Here’s how the constant is used in code:

Function TotalAmount(curSaleAmount As Currency)
   TotalAmount = curSaleAmount * MTAXRATE
End Function

This routine multiplies the curSaleAmount, received as a parameter, by the constant MTAXRATE. It returns the result of the calculation by setting the function name equal to the product of the two values. The advantage of the constant in this example is that the code is more readable than TotalAmount = curSaleAmount * .0875 would be.

Scoping Symbolic Constants

Just as regular variables have scope, user-defined constants have scope. In the preceding example, you created a Private constant. The following statement, when placed in a module’s Declarations section, creates a Public constant:

Public Const GTAXRATE As Currency = 0.0875

Because this constant is declared as Public, it can be accessed from any subroutine or function (including event routines) in your entire application. To better understand the benefits of a Public constant, suppose that you have many functions and subroutines, all referencing the constant GTAXRATE. Imagine what would happen if the tax rate were to change. If you hadn’t used a constant, you would need to search your entire application, replacing the old tax rate with the new tax rate. However, because your Public constant is declared in one place, you can easily go in and modify the one line of code where this constant is declared.

Note

By definition, the value of constants can’t be modified at runtime. If you try to modify the value of a constant, you get this VBA compiler error:

Variable Required - can't assign to this expression

Figure 12.1 illustrates this message box. You can see that an attempt was made to modify the value of the constant called GTAXRATE, which resulted in a compile error.

An error message resulting from trying to modify the value of a constant.

Figure 12.1. An error message resulting from trying to modify the value of a constant.

If you must change the value at runtime, you should consider storing the value in a table rather than declaring it as a constant. You can read the value into a variable when the application loads, and then modify the variable if needed. If you choose, you can write the new value back to the table.

Working with Intrinsic Constants

Microsoft Access declares a number of intrinsic constants that can be used in Code, Form, and Report modules. Because they’re reserved by Microsoft Access, you can’t modify their values or reuse their names; however, they can be used at any time without being declared.

You should use intrinsic constants whenever possible in your code. Besides making your code more readable, they make your code more portable to future releases of Microsoft Access. Microsoft might change the value associated with a constant, but it isn’t likely to change the constant’s name. All intrinsic constants appear in the Object Browser; to activate it, simply click the Object Browser tool on the Visual Basic toolbar while in the VBE. To view the constants that are part of the VBA language, select VBA from the Object Browser’s Project/Library drop-down list. Click Constants in the Classes list box, and a list of those constants is displayed in the Members of ‘Constants’ list box. (See Figure 12.2.)

Using the Object Browser to view intrinsic constants.

Figure 12.2. Using the Object Browser to view intrinsic constants.

In Figure 12.2, all the constant names begin with vb. All VBA constants are prefixed with vb; all ActiveX Data Object constants, with ad; all Data Access Object (DAO) constants, with db; and all constants that are part of the Access language are prefixed with ac. To view the Access language constants, select Access from the Project/Library drop-down list and Constants from the Classes list box. To view the ActiveX Data Object constants, select ADODB from the Project/Library drop-down list. The constants are categorized by their function into various classes (for example, LockTypeEnum and ExecuteOptionEnum). Select the appropriate class from the Classes list box, and its members appear in the Members Of list box.

Another way to view constants is within the context of the parameter you’re working with in the Code window. Right-click after the name of a parameter and select List Constants to display the constants associated with the parameter. This feature is covered in detail in Chapter 7, “VBA: An Introduction” in the section entitled “Tools for Working in the Visual Basic Editor.”

Working with Arrays

An array is a series of variables referred to by the same name. Each element of the array is differentiated by a unique index number, but all the elements must be of the same data type. Arrays help make coding more efficient. It’s easy to loop through each element of an array, performing some process on each element. Arrays have a lower bound, which is zero by default, and an upper bound, and array elements must be contiguous.

The scope of an array can be Public, Module, or Local. As with other variables, this depends on where the array is declared and whether the Public, Private, or Dim keyword is used.

Declaring and Working with Fixed Arrays

When declaring a fixed array, you give VBA the upper bound and the type of data that it will contain. The following code creates an array that holds six string variables:

Dim astrNames(5) As String

Fixed means that this array’s size can’t be altered at runtime. The following code gives an example of how you can loop through the array:

Sub FixedArray()
    'Declare an array of six elements
    Dim astrNames(5) As String
    Dim intCounter As Integer

    'Populate the first four elements of the array
    astrNames(0) = "Dan"
    astrNames(1) = "Alexis"
    astrNames(2) = "Brendan"
    astrNames(3) = "Zachary"

    'Use a For...Next loop to loop through the
    'elements of the array
    For intCounter = 0 To UBound(astrNames)
        Debug.Print astrNames(intCounter)
    Next intCounter
End Sub

This code starts by storing values into the first four elements of a six-element array. It then loops through each element of the array, printing the contents. Notice that the For...Next loop starts at zero and goes until the upper bound of the array, which is (5). Because the array is made up of strings, the last two elements of the array contain zero-length strings. If the array was composed of integers, the last two elements would contain zeros.

Another way to traverse the array is to use the For Each...Next construct. Your code would look like this:

Sub ArrayWith()
    'Declare an array of six elements
    Dim astrNames(5) As String
    Dim intCounter As Integer
    Dim vntAny As Variant

    'Populate the first four elements of the array
    astrNames(0) = "Dan"
    astrNames(1) = "Alexis"
    astrNames(2) = "Brendan"
    astrNames(3) = "Zachary"

    'Use a For...Each loop to loop through the
    'elements of the array
    For Each vntAny In astrNames
        Debug.Print vntAny
    Next vntAny
End Sub

This code declares a Variant variable called vntAny. Instead of using a loop with Ubound as the upper delimiter to traverse the array, the example uses the For Each...Next construct.

Note

Many people do not like the fact that, by default, the elements of an array are zero based. Fortunately, the VBA language allows you to declare both the lower bound and the upper bound of any array. The syntax looks like this:

Dir astrNames(1 to 6)

Declaring and Working with Dynamic Arrays

Often, you don’t know how many elements your array needs to contain. In this case, you should consider declaring a dynamic array, which can be resized at runtime. Using this type of array can make your code more efficient because VBA preallocates memory for all elements of a fixed array, regardless of whether data is stored in each of the elements. However, if you aren’t sure how many elements your array will contain, preallocating a huge amount of memory can be quite inefficient.

To create a dynamic array, you declare it without assigning an upper bound. You do this by omitting the number between the parentheses when declaring the array, as shown in this example:

Sub DynamicArray()
    'Declare a dynamic array
    Dim astrNames() As String
    Dim intCounter As Integer
    Dim vntAny As Variant

    'Resize the array to hold two elements
    ReDim astrNames(1)
    'Populate the two elements
    astrNames(0) = "Dan"
    astrNames(1) = "Alexis"

    'Use a For...Each loop to loop through the
    'elements of the array
    For Each vntAny In astrNames
        Debug.Print vntAny
    Next vntAny
End Sub

However, there’s a potential problem when you try to resize the array:

Sub ResizeDynamic()
    'Declare a dynamic array
    Dim astrNames() As String
    Dim intCounter As Integer
    Dim vntAny As Variant

    'Resize the array to hold two elements
    ReDim astrNames(1)

    'Populate the two elements
    astrNames(0) = "Dan"
    astrNames(1) = "Alexis"

    'Use a For...Each loop to loop through the
    'elements of the array
    For Each vntAny In astrNames
        Debug.Print vntAny
    Next vntAny
End Sub

Sub ResizeDynamic()
    'Declare a dynamic array
    Dim astrNames() As String
    Dim intCounter As Integer
    Dim vntAny As Variant

    'Resize the array to hold two elements
    ReDim astrNames(1)

    'Populate the two elements
    astrNames(0) = "Dan"
    astrNames(1) = "Alexis"
    'Resize the array to hold four elements
    ReDim astrNames(3)

    'Populate the last two elements
    astrNames(2) = "Brendan"
    astrNames(3) = "Zachary"

    'Use a For..Each loop to loop through the
    'elements of the array
    For Each vntAny In astrNames
        Debug.Print vntAny
    Next vntAny
End Sub

You might expect that all four elements will contain data. Instead, the ReDim statement reinitializes all the elements, and only elements 2 and 3 contain values. This problem can be avoided by using the Preserve keyword. The following code behaves quite differently:

Sub ResizePreserve()
    'Declare a dynamic array
    Dim astrNames() As String
    Dim intCounter As Integer
    Dim vntAny As Variant

    'Resize the array to hold two elements
    ReDim astrNames(1)

    'Populate the two elements
    astrNames(0) = "Dan"
    astrNames(1) = "Alexis"

    'Resize the array to hold four elements
    ReDim Preserve astrNames(3)

    'Populate the last two elements
    astrNames(2) = "Brendan"
    astrNames(3) = "Zachary"

    'Use a For...Each loop to loop through the
    'elements of the array
    For Each vntAny In astrNames
        Debug.Print vntAny
    Next vntAny
End Sub

In this example, all values already stored in the array are preserved. The Preserve keyword brings its own difficulties, though. It can temporarily require huge volumes of memory because, during the ReDim process, VBA creates a copy of the original array. All the values from the original array are copied to a new array. The original array is removed from memory when the process is complete. The Preserve keyword can cause problems if you’re dealing with very large arrays in a limited memory situation.

Tip

Each type of array complements the other’s drawbacks. As a VBA developer, you have the flexibility of choosing the right type of array for each situation. Fixed arrays are the way to go when the number of elements doesn’t vary widely. But, dynamic arrays should be used when the number varies widely, and you’re sure you have enough memory to resize even the largest possible arrays.

Passing Arrays as Parameters

Many people are unaware that you can pass an array as a parameter to a function or subroutine. The following code provides an example:

Sub PassArray()
    'Declare a six-element array
    Dim astrNames(5) As String
    Dim intCounter As Integer

    'Call the FillNames function, passing a reference
    'to the array
    Call FillNames(astrNames)

    'Use a For...Next loop to loop through the
    'elements of the array
    For intCounter = 0 To UBound(astrNames)
        Debug.Print astrNames(intCounter)
    Next intCounter
End Sub

The code begins by declaring a fixed array called astrNames. The FillNames routine is called. It receives the array as a parameter and then populates all its elements. The PassArray routine is then able to loop through all the elements of the array that was passed, displaying information from each element. The FillNames routine looks like this:

Sub FillNames(varNameList As Variant)
    'Populate the elements of the array
    varNameList(0) = "Alison"
    varNameList(1) = "Dan"
    varNameList(2) = "Alexis"
    varNameList(3) = "Brendan"
    varNameList(4) = "Zachary"
    varNameList(5) = "Sonia"
End Sub

Notice that the routine receives the array as a variant variable. It then populates each element of the array.

Advanced Function Techniques

The advanced function techniques covered in this section allow you to get the most out of the procedures you build. First, you learn the difference between passing your parameters by reference and passing them by value, and see that the default method of passing parameters isn’t always the most prudent method.

The second part of this section shows you how to work with optional parameters, which help you build flexibility into your functions. They let you omit parameters, but named parameters allow you to add readability to your code. Named parameters also shelter you from having to worry about the order in which the parameters must appear. After reading this section, you can build much more robust and easy-to-use functions.

Passing by Reference Versus Passing by Value

By default, parameters in Access are passed by reference. This means that a memory reference to the variable being passed is received by the function. This process is best illustrated by an example:

Sub PassByRef()
    'Declare string variables
    Dim strFirstName As String
    Dim strLastName As String

    'Assign values to the string variables
    strFirstName = "Alison"
    strLastName = "Balter"

    'Call a subroutine that receives the two variables as
    'parameters by reference
    Call FuncByRef(strFirstName, strLastName)

    'Print the changed values of the variables
    Debug.Print strFirstName
    Debug.Print strLastName
End Sub
Sub FuncByRef(strFirstParm As String, strSecondParm As String)
    'Modify the values of the parameters
    strFirstParm = "Bill"
    strSecondParm = "Gates"
End Sub

You might be surprised that the Debug.Print statements found in the subroutine PassByRef print "Bill" and "Gates". This is because strFirstParm is actually a reference to the same location in memory as strFirstName, and strSecondParm is a reference to the same location in memory as strLastName. This violates the concepts of black-box processing, in which a variable can’t be changed by any routine other than the one it was declared in. The following code eliminates this problem:

Sub PassByVal()
    'Declare the string variables
    Dim strFirstName As String
    Dim strLastName As String

    'Assign values to the string variables
    strFirstName = "Alison"
    strLastName = "Balter"

    'Call a subroutine that receives the two variables as
    'parameters by value
    Call FuncByVal(strFirstName, strLastName)

    'Print the unchanged values of the variables
    Debug.Print strFirstName
    Debug.Print strLastName
End Sub

Sub FuncByVal(ByVal strFirstParm As String, _
ByVal strSecondParm As String)
    'Change the values of the parameters
    'Since they are received by value,
    'the original variables are unchanged
    strFirstParm = "Bill"
    strSecondParm = "Gates"
End Sub

This FuncByVal subroutine receives the parameters by value. This means that only the values in strFirstName and strLastName are passed to the FuncByVal routine. The strFirstName and strLastName variables, therefore, can’t be modified by the FuncByVal subroutine. The Debug.Print statements print "Alison" and "Balter".

The following example illustrates a great reason why you might want to pass a parameter by reference:

Sub GoodPassByRef()
    'Declare variables
    Dim blnSuccess As Boolean
    Dim strName As String

    'Set the value of the string variable
    strName = "Microsoft"

    'Set the boolean variable equal to the value
    'returned from the GoodFunc function
    blnSuccess = GoodFunc(strName)

    'Print the value of the boolean variable
    Debug.Print blnSuccess
End Sub

Function GoodFunc(strName As String)
    'Evaluate the length of the value received
    'as a parameter
    'Convert to Upper Case and return true if not zero-length
    'Return false if zero-length
    If Len(strName) Then
        strName = UCase$(strName)
        GoodFunc = True
    Else
        GoodFunc = False
    End If

End Function

In essence, the GoodFunc function needs to return two values. Not only does the function need to return the uppercase version of the string passed to it, but it also needs to return a success code. Because a function can return only one value, you need to be able to modify the value of strName within the function. As long as you’re aware of what you’re doing and why you’re doing it, there’s no problem with passing a parameter by reference.

Tip

I use a special technique to help readers of my code see whether I’m passing parameters by reference or by value. When passing parameters by reference, I refer to the parameters by the same name in both the calling routine and the actual procedure that I’m calling. On the other hand, when passing parameters by value, I refer to the parameters by different names in the calling routine and in the procedure that’s being called.

After reading this section, you might ask yourself whether it is better to pass parameters by reference or by value. Although in terms of “black-box” processing, it is better to pass by value, code that involves parameters passed by reference actually executes more quickly than those passed by value. As long as you and the programmers that you work with are aware of the potential problems with passing parameters by reference, in general, I feel that it is better to pass parameters by reference.

Optional Parameters: Building Flexibility into Functions

Access 97, Access 2000, and Access 2002 allow you to use optional parameters. In other words, it isn’t necessary to know how many parameters will be passed. The ReturnInit function in the following code receives the second two parameters as optional; it then evaluates whether the parameters are missing and responds accordingly:

Function ReturnInit(ByVal strFName As String, _
       Optional ByVal strMI, Optional ByVal strLName)
    'If strMI parameter is not received, prompt user for value
    If IsMissing(strMI) Then
        strMI = InputBox("Enter Middle Initial")
    End If

    'If strLName parameter is not received, prompt user for value
    If IsMissing(strLName) Then
        strLName = InputBox("Enter Last Name")
    End If

    'Return concatenation of last name, first name,
    'and middle initial
    ReturnInit = strLName & "," & strFName & " " & strMI
End Function

This function could be called as follows:

strName = ReturnInit("Bill",,"Gates")

As you can see, the second parameter is missing. Rather than causing a compiler error, as in earlier versions of Access, this code compiles and runs successfully. The IsMissing function, built into Access, determines whether a parameter has been passed. After identifying missing parameters, you must decide how to handle the situation in code. In the example, the function prompts for the missing information, but here are some other possible choices:

  • Insert default values when parameters are missing.

  • Accommodate for the missing parameters in your code.

Listing 12.1 and Listing 12.2 illustrate how to carry out these two alternatives.

Example 12.1. Inserting Default Values When Parameters Are Missing

Function ReturnInit2(ByVal strFName As String, _
       Optional ByVal strMI, Optional ByVal strLName)
    'If middle initial is not received, set it to "A"
    If IsMissing(strMI) Then
        strMI = "A"
    End If

    'If last name is not received, set it to "Roman"
    If IsMissing(strLName) Then
        strLName = "Roman"
    End If

    'Return concatenation of last name, first name,
    'and middle initial
    ReturnInit2 = strLName & "," & strFName & " " & strMI
End Function

This example uses a default value of "A" for the middle initial and a default last name of "Roman". Now take a look at Listing 12.2, which illustrates another method of handling missing parameters.

Example 12.2. Accommodating for Missing Parameters in Your Code

Function ReturnInit3(ByVal strFName As String, _
       Optional ByVal strMI, Optional ByVal strLName)
    Dim strResult As String

    'If middle initial and last name are missing,
    'return first name
    If IsMissing(strMI) And IsMissing(strLName) Then
        ReturnInit3 = strFName

    'If only the middle initial is missing
    'return last name and first name
    ElseIf IsMissing(strMI) Then
        ReturnInit3 = strLName & ", " & strFName

    'If only the last name is missing
    'return first name and middle initial
    ElseIf IsMissing(strLName) Then
        ReturnInit3 = strFName & " " & strMI

    'Otherwise (If nothing is missing),
    'return last name, first name and middle initial
    Else
        ReturnInit3 = strLName & "," & strFName & " " & strMI
    End If
End Function

This example manipulates the return value, depending on which parameters it receives. If neither optional parameter is passed, just the first name displays. If the first name and middle initial are passed, the return value contains the first name followed by the middle initial. If the first name and last name are passed, the return value contains the last name, a comma, and the first name. If all three parameters are passed, the function returns the last name, a comma, a space, and the first name.

The declaration of the ReturnInit3 function shown in Listing 12.2 can easily be modified to provide default values for each optional parameter. The following declaration illustrates this:

Function ReturnInit4(Optional ByVal strFName As String = "Alison", _
       Optional ByVal strMI As String = "J", _
       Optional ByVal strLName As String = "Balter")

ReturnInit4 has three optional parameters. The declaration assigns a default value to each parameter. The function uses the default value if the calling routine does not supply the parameter.

Note

It is important to note that the IsMissing function only works with parameters with a data type of variant. This is because the IsMissing function returns true only if the value of the parameter is empty. If the parameter is numeric (for example, an integer), you will need to test for zero. If the parameter is a string, you will need to test for a zero-length string ("").

Named Parameters: Eliminate the Need to Count Commas

In all the examples you’ve seen so far, the parameters of a procedure have been supplied positionally. Named parameters allow you to supply parameters without regard for their position, which is particularly useful in procedures that receive optional parameters. Take a look at this example:

strName = ReturnInit3("Bill",,"Gates")

Because the second parameter isn’t supplied, and the parameters are passed positionally, a comma must be used as a placemarker for the optional parameter. This requirement can become unwieldy when you’re dealing with several optional parameters. The following example greatly simplifies the process of passing the parameters and also better documents what’s happening:

strName = ReturnInit3(strFName:= "Bill",strLName:= "Gates")

When parameters are passed by name, it doesn’t even matter in what order the parameters appear, as shown in the following example:

strName = ReturnInit3(strLName:= "Gates",strFName:="Bill")

This call to the ReturnInit3 function yields the same results as the call to the function in the previous example.

Note

When using named parameters, each parameter name must be exactly the same as the name of the parameter in the function being called. Besides requiring intimate knowledge of the function being called, this method of specifying parameters has one important disadvantage: If the author of the function modifies a parameter’s name, all routines that use the named parameter will fail when calling the function.

Recursive Procedures

A recursive procedure is one that calls itself. If a procedure calls itself over and over again, it will eventually render an error. This is because it runs out of stack space. Here’s an example:

Function Recursive(lngSomeVal)
    'Return value based on another call to the function
    Recursive = Recursive(lngSomeVal)
End Function

There are practical reasons why you might want to call a function recursively. Here’s an example:

Function GetFactorial(intValue as Integer) as Long
    'If value passed is less than or equal to one, we're done
    If intValue <= 1 Then
        GetFactorial = 1

    'If value passed is greater than one,
    'call function again with decremented value
    'and multiply by value
    Else
        GetFactorial = GetFactorial(intValue - 1) * intValue
    End If
End Function

The code receives an input parameter (for example, 5). The value is evaluated to see if it is less than or equal to 1. If it is, the function is exited. If the value is greater than 1, the function is called again, but is passed the previous input parameter minus 1 (for example, 4). The return value from the function is multiplied by the original parameter value (for example, 4*5). The function calls itself over and over again until the value that it passes to itself is 2 minus 1 (1), and the function is exited. In the example where 5 is passed to the function, it multiplies 5*4*3*2*1, resulting in 120, the factorial of 5.

Using Parameter Arrays

Using a parameter array, you can easily pass a variable number of arguments to a procedure. Here’s an example:

Sub GetAverageSalary(strDepartment As String, _
    ParamArray currSalaries() As Variant)

    Dim sngTotalSalary As Single
    Dim sngAverageSalary As Single
    Dim intCounter As Integer

    'Loop through the elements of the array,
    'adding up all of the salaries
    For intCounter = 0 To UBound(currSalaries())
        sngTotalSalary = sngTotalSalary + currSalaries(intCounter)
    Next intCounter

    'Divide the total salary by the number of salaries in the array
    sngAverageSalary = sngTotalSalary / (UBound(currSalaries()) + 1)

    'Display the department and the average salary in a message box
    MsgBox strDepartment & " has an average salary of " & _
        sngAverageSalary
End Sub

The routine is called like this:

Call GetAverageSalary("Accounting", 60000, 20000, 30000, 25000, 80000)

The beauty of the ParamArray keyword is that you can pass a variable number of parameters to the procedure. In the example, a department name and a variable number of salaries are passed to the GetAverageSalary procedure. The procedure loops through all the salaries that it receives in the parameter array, adding them together. It then divides the total by the number of salaries contained in the array.

Working with Empty and Null

Empty and Null are values that can exist only for Variant variables. They’re different from one another and different from zero or a zero-length string. At times, you need to know whether the value stored in a variable is zero, a zero-length string, Empty, or Null. You can make this differentiation only with Variant variables.

Working with Empty

Variant variables are initialized to the value of Empty. Often, you need to know whether a value has been stored in a Variant variable. If a Variant has never been assigned a value, its value is Empty. As mentioned, the Empty value is not the same as zero, Null, or a zero-length string.

It’s important to be able to test for Empty in a runtime environment. This can be done by using the IsEmpty function, which determines whether a variable has the Empty value. The following example tests a String variable for the Empty value:

Sub StringVar()
    Dim strName As String
    Debug.Print IsEmpty(strName) 'Prints False
    Debug.Print strName = "" 'Prints True
End Sub

The Debug.Print statement prints False. This variable is equal to a zero-length string because the variable is initialized as a String variable. All String variables are initialized to a zero-length string. The next example tests a Variant variable to see whether it has the Empty value:

Sub EmptyVar()
    Dim vntName As Variant
    Debug.Print IsEmpty(vntName) 'Prints True
    vntName = ""
    Debug.Print IsEmpty(vntName) 'Prints False
    vntName = Empty
    Debug.Print IsEmpty(vntName) 'Prints True
End Sub

A Variant variable loses its Empty value when any value has been stored in it, including zero, Null, or a zero-length string. It can become Empty again only by storing the keyword Empty in the variable.

Working with Null

Null is a special value that indicates unknown or missing data. Null is not the same as Empty, nor is one Null value equal to another one. Variant variables can contain the special value called Null.

Often, you need to know whether specific fields or controls have never been initialized. Uninitialized fields and controls have a default value of Null. By testing for Null, you can make sure fields and controls contain values.

If you want to make sure that all fields and controls in your application have data, you need to test for Nulls. This can be done by using the IsNull function:

Sub NullVar()
    Dim vntName As Variant
    Debug.Print IsEmpty(vntName) 'Prints True
    Debug.Print IsNull(vntName) 'Prints False
    vntName = Null
    Debug.Print IsNull(vntName) 'Prints True
End Sub

Notice that vntName is equal to Null only after the value of Null is explicitly stored in it. It’s important to know not only how variables and Null values interact, but also how to test for Null within a field in your database. A field contains a Null if data hasn’t yet been entered in the field, and the field has no default value. In queries, you can test for the criteria "Is Null" to find all the records in which a particular field contains a Null value. When dealing with recordsets (covered in Chapter 14, “What Are ActiveX Data Objects and Data Access Objects, and Why Are They Important?”), you can also use the IsNull function to test for a Null value in a field. Here’s an example:

Sub LoopProjects()
    Dim rst As ADODB.Recordset
    Set rst = New ADODB.Recordset

    'Open a recordset based on the projects table
    rst.Open "tblProjects", CurrentProject.Connection

    'Loop through all of the records in the recordset
    Do Until rst.EOF

        'Print the ProjectID and the ProjectName
        Debug.Print rst!ProjectID, rst!ProjectName

        'If the ProjectBeginDate field is null,
        'display a message to the user
        If IsNull(rst!ProjectBeginDate) Then
            Debug.Print "Project Begin Date Contains No Value!!"
        End If

       'Move to the next row in the recordset
       rst.MoveNext
    Loop
End Sub

Alternatively, you could use the more compact Nz function to detect Nulls and print a special message:

Sub LoopProjects2()
    Dim rst As ADODB.Recordset
    Set rst = New ADODB.Recordset

    'Open a recordset based on the projects table
    rst.Open "tblProjects", CurrentProject.Connection

    'Loop through all of the rows in the recordset
    Do Until rst.EOF

        'Print the ProjectID and the ProjectName
        Debug.Print rst!ProjectID, rst!ProjectName

        'Print the ProjectBeginDate, or a message if
        'the ProjectBeginDate is null
        Debug.Print Nz(rst!ProjectBeginDate, _
            "Project Begin Date Contains No Value!!")
        rst.MoveNext
    Loop
End Sub

All the concepts of recordset handling are covered in Chapter 14. For now, you need to understand only that this code loops through each record in tblProjects. It uses the IsNull function to evaluate whether the ProjectBeginDate field contains a Null value. If the field does contain a Null, a warning message is printed to the Immediate window. Here is another example:

Private Sub Form_Current()

    Dim ctl as Control

    'Loop through each control in the form's
    'Controls collection
    For Each ctl In Controls

        'If the control is a TextBox
        If TypeOf ctl Is TextBox Then
           'If the value in the control is null,
           'change the BackColor property to cyan
           If IsNull(ctl.Value) Then
               ctl.BackColor = vbCyan

           'If the value in the control is not null
           'change the BackColor property to white
           Else
               ctl.BackColor = vbWhite
           End If
       End If
    Next ctl
End Sub

The code in this example (found in the frmProjects form in CHAP12EX.MDB) loops through every control on the current form. If the control is a text box, the routine checks to see whether the value in the text box is Null. If it is, the BackColor property of the text box is set to Aqua; otherwise, it’s set to White.

You should know about some idiosyncrasies of Null:

  • Expressions involving Null always result in Null. (See the next example.)

  • A function that’s passed a Null usually returns a Null.

  • Null values propagate through built-in functions that return variants.

The following example shows how Null values are propagated:

Sub PropNulls()
    Dim rst As ADODB.Recordset
    Set rst = New ADODB.Recordset

    'Open a recordset based on the Projects table
    rst.Open "tblProjects", CurrentProject.Connection

    'Loop through the recordset
    Do Until rst.EOF

        'Print the ProjectID and the value of the
        'ProjectBeginDate plus one
        Debug.Print rst!ProjectID, rst!ProjectBeginDate + 1

        'Move to the next row
        rst.MoveNext
    Loop
End Sub

Figure 12.3 illustrates the effects of running this routine on a table in which the first and third records contain Null values. Notice that the result of the calculation is Null for those records because the Null propagated within those records.

The result of running the PropNulls routine.

Figure 12.3. The result of running the PropNulls routine.

Notice the difference from the previous example if the value in the field is Empty:

Sub EmptyVersusNull()
    Dim rst As ADODB.Recordset
    Set rst = New ADODB.Recordset

    'Open a recordset based on the Projects table
    rst.Open "tblProjects", CurrentProject.Connection

    'Loop through the recordset
    Do Until rst.EOF

        'Print the ProjectID and the PurchaseOrderNumber
        'combined with the word "Hello"
        Debug.Print rst!ProjectID, rst!PurchaseOrderNumber + "Hello"

        'Move to the next row
        rst.MoveNext
    Loop
End Sub

In this example, the tblProjects table has several records. The PurchaseOrderNumber for the first record contains a Null; for the third record, it contains an Empty. Notice the different effects of the two values, as shown in Figure 12.4.

The result of running the EmptyVersusNull routine.

Figure 12.4. The result of running the EmptyVersusNull routine.

Looking at Figure 12.4, you can see that Null printed for the first record, and Hello printed for the third record.

Note

The EmptyVersusNull routine uses a numeric operator (+). As discussed, the effect of Null used in a calculation is a resulting Null. In text strings, you can use an ampersand (&) instead of a plus (+) to eliminate this problem. Figure 12.5 illustrates the same code with an ampersand to concatenate rather than add. You can see that no Null values result from the concatenation.

The result of changing plus (+) in the EmptyVersusNull routine to an ampersand (&).

Figure 12.5. The result of changing plus (+) in the EmptyVersusNull routine to an ampersand (&).

It’s very common to create a generic routine that receives any value, tests to see whether it’s Null, and returns a non-Null value. An example is the CvNulls function:

Function CvNulls(vntVar1 As Variant, vntVar2 As Variant) _
      As Variant
    'If first variable is null, return the second variable
    'otherwise, return the first variable
    CvNulls = IIf(IsNull(vntVar1), vntVar2, vntVar1)
End Function

This routine would be called as follows:

Sub TestForNull(vntSalary As Variant, vntCommission As Variant)
    'Add the result of calling the CVNulls function,
    'passing the salary and zero to the
    'result of calling the CVNulls function
    'passing the commission and zero
    curTotal = CvNulls(vntSalary, 0) + CvNulls(vntCommission, 0)

    'Display the total of salary plus commission
    MsgBox curTotal
End Sub

The TestForNull routine receives two parameters: salary and commission. It adds the two values to determine the total of salaries plus commissions. Ordinarily, if the value of either parameter is Null, the expression results in Null. This problem is eliminated by the CvNulls function, which also receives two parameters. The first parameter is the variable being tested for Null; the second is the value you want the function to return if the first parameter is determined to be Null. The routine combines the Immediate If (IIf) function and the IsNull function to evaluate the first parameter and return the appropriate value.

Note

If you simply want to substitute a value for a Null, the built-in function Nz can be used instead of a user-defined function. The user-defined function offers more functionality, when necessary.

Creating and Working with Custom Collections

Earlier in this chapter, I discussed the problems associated with arrays. If you are unsure of the number of elements that the array will contain, fixed arrays can take up large amounts of memory unnecessarily. On the other hand, the resizing of dynamic arrays is rather inefficient. Finally, all the elements of an array must be contiguous, and the arbitrary identifier for the array element is meaningless. The answer—custom collections. Custom collections can contain values and objects. You can easily add items to, and remove items from, a collection. Each element in the collection is identified by a meaningful unique key.

In summary, custom collections are similar to arrays, but they offer several advantages:

  • Collections are dynamically allocated. They take up memory based only on what’s in them at a given time. This is different from arrays, whose size must be either predefined or redimensioned at runtime. When an array is redimensioned, Access actually makes a copy of the array in memory, taking up substantial resources. By using custom collections, you can avoid this consumption of extra resources.

  • A collection always knows how many elements it has, and elements can easily be added and removed.

  • Each element of a collection can contain a different type of data.

  • Elements can be added into any element of a collection.

Note

The code examples in this section are found in the basCollections module of the Chap12EX.MDB database.

Note

Creating a Collection

A collection is created using a Collection object. After the Collection object is declared, items can be added to the collection. The code necessary to create a custom collection looks like this:

Dim colNames as Collection

Adding Items to a Collection

The Add method of the Collection object is used to add items to a custom collection. The Add method receives a value or object reference as its first parameter, and a unique key to that element of the collection as a second parameter. The Add method appears as follows:

colNames.Add "Alexis", "Alexis"

The code shown previously adds the name Alexis to a collection called colNames. The key to the item in the collection is the name Alexis. In the following code example, the collection colNames is first declared and instantiated. Then several names are added to the custom collection colNames.

Sub AddToCollection()
    'Declare a Collection object
    Dim colNames As Collection
    'Instantiate the Collection object
    Set colNames = New Collection

    'Add items to the collection
    colNames.Add "Alison", "Alison"
    colNames.Add "Dan", "Dan"
    colNames.Add "Alexis", "Alexis"
    colNames.Add "Brendan", "Brendan"
    colNames.Add "Sonia", "Sonia"
    colNames.Add "Sue", "Sue"
End Sub

Caution

Unlike almost every other array or collection in VBA, custom collections are one-based rather than zero-based. This is a big change if you’re used to thinking of arrays and collections as always zero-based.

Accessing an Item in a Collection

After items have been added to a collection, the Item method is used to access them via either their ordinal position, or the key designated when they were added. Accessing an item in a collection using the ordinal position looks like this:

Debug.Print colNames.Item(1)

Because the Item method is the default method of the Collection object, the code can be shortened to this:

Debug.Print colNames(1)

I usually prefer to refer to an item in a collection using its unique key. The code appears as follows:

Debug.Print colNames("Alexis")

Removing Items from a Collection

The Remove method of the Collection object is used to remove items from a collection. The syntax looks like this:

colNames.Remove 2

The preceding syntax would remove the second element of the collection. Using the key, the code is changed to this:

colNames.Remove "Sonia"

You can easily remove all the elements of a collection in two ways:

Set colNames = New Collection

or

Set colNames = Nothing

Iterating Through the Elements of a Collection

The For...Each loop is used to iterate through the items in a collection. The code looks like this:

Sub IterateCollection()
    'Declare a Collection object
    Dim colNames As Collection

    'Declare a variant variable for looping
    'through the collection
    Dim varItem As Variant

    'Instantiate the Collection object
    Set colNames = New Collection

    colNames.Add "Alison", "Alison"
    colNames.Add "Dan", "Dan"
    colNames.Add "Alexis", "Alexis"
    colNames.Add "Brendan", "Brendan"
    colNames.Add "Sonia", "Sonia"
    colNames.Add "Sue", "Sue"

    'Use the variant variable and a For..Each
    'loop to loop through each element in
    'the collection, printing its value
    For Each varItem In colNames
        Debug.Print colNames(varItem)
    Next varItem
End Sub

Notice that in addition to the declaration of the Collection variable, a Variant variable is declared. The Variant variable is used in the For...Each loop to loop through each item in the collection. The Variant variable is the subscript within the For...Each loop for accessing a particular item within the collection.

Low-Level File Handling

On occasion, it is necessary to write data to, or read data from, a text file. This is often referred to as low-level file handling. Three types of file access exist: sequential, random, and binary. Only sequential access is covered in this text. Sequential access is used to read and write to a text file, such as an error log. The Open keyword is used to open a text file. The Input # keyword is used to read data. The Write # keyword is used to write data. Finally, the Close keyword is used to close the file. Here’s an example:

Sub LogErrorText()
    Dim intFile As Integer

    'Store a free file handle into a variable
    intFile = FreeFile

    'Open a file named ErrorLog.txt in the current directory
    'using the file handle obtained above
    Open CurDir & "ErrorLog.Txt" For Append Shared As intFile

    'Write the error information to the file
    Write #intFile, "LogErrorDemo", Now, Err, Error, CurrentUser()

    'Close the file
    Close intFile
End Sub

The FreeFile function is used to locate a free file handle. The Open keyword opens a file located in the current directory, with the name ErrorLog.txt. The file is open in shared mode and for append, using the file handle returned by the FreeFile function. The Write # keyword is then used to write error information to the text file. Finally, the Close keyword closes the text file.

Note

This example is taken from Chapter 14. The sample code is located in the CHAP14EX.mdb database.

Understanding and Effectively Using Compilation Options

Microsoft Access gives you a few alternatives for compilation. Understanding them can help you to decide whether compilation speed or trapping compilation errors is more important to you.

Compile on Demand

By default, VBA compilesyour code only when the code in the module changes or when a procedure in one module is called by another module. Although this default setting can dramatically speed the compilation process, it can leave you wondering whether you have a hidden time bomb lurking somewhere in your application.

Here’s a typical scenario: You open a form, make some simple changes, save the changes, and close the form. You repeat this process for a few additional forms. You also open a couple of modules to make some equally simple changes. During the testing process, you forget to test one or more of the forms and one or more of the modules. With the Compile On Demand option set to True (its default value), errors aren’t identified until the offending code is accessed.

To disable the Compile On Demand feature, choose Tools|Options from the VBE. Click the General tab and remove the check from Compile On Demand. You might notice some degradation in performance each time your code compiles, but this is time well spent.

Importing and Exporting Code Modules

The Access 2002 VBE allows you to import code or form modules into, and export code modules from, a database. To export a form or code module, take the following steps:

  1. Activate the VBE.

  2. Within the Project Explorer window, right-click the object you want to export.

  3. Select Export File. The Export File dialog appears.

  4. Select a location and name for the exported file and then click Save.

When you export a module, it is exported as an ASCII text file. You can import the text file into another Microsoft Access database, into any other Microsoft Office product (for example, Microsoft Excel), or into a Visual Basic project.

Note

If you export a Form module from the VBE, only the Class module behind the form is exported. No visual aspects of the form are exported.

Just as you can export a text file, you can import a text file. This allows you to add an existing module or form to a project. The file is copied and imported into the database. The original file is unaffected. To import a file into your Access database:

  1. Activate the VBE.

  2. Within the Project Explorer window, right-click and select Import File. The Import File dialog appears.

  3. Locate and select the file you want to import, and then click Open.

Working with Project Properties

Every database project has user-definable properties. These include:

  • The project name

  • A description of the project

  • The name of the help file associated with the project

  • The help context ID associated with the project

  • Conditional compilation arguments

  • A password associated with the project

To view or modify project properties:

  1. Activate the VBE.

  2. Select Tools|<project name> Properties. The Project Properties dialog appears (see Figure 12.6).

    The Project Properties dialog.

    Figure 12.6. The Project Properties dialog.

  3. Click the General tab to designate or change any of the general project properties.

  4. Click the Protection tab to specify a password for the VBA project.

  5. Click OK to close the dialog, accepting the options you have set. You must close the database and reopen it for any security options to take effect.

The Protection options deserve special attention. If you click to select Lock Project for Viewing, the VBA project cannot be viewed or edited by someone who does not have the correct password. If you do not select Lock Project for Viewing, the VBA project can be viewed by anyone, but project properties can be changed only by someone with the correct password.

Examples of Null, the DoCmd Object, and Intrinsic Constants

The following event routine illustrates how you could view all the projects associated with the selected client. It illustrates the importance of the ability to work with Null values and intrinsic constants.

Private Sub cmdViewProjects_Click()
On Error GoTo Err_cmdViewProjects_Click

    'Evaluate the ClientID text box to determine if it is null
    'If it is null, display a message to the user
    'Otherwise, save the current record and open the
    'projects form (which is set up to only show projects
    'related to the selected client)
    If IsNull(Me.txtClientID.Value) Then
        MsgBox "You Must Enter Client Information Before " & _
            "Viewing the Projects Form"
    Else
        DoCmd.RunCommand acCmdSaveRecord
        DoCmd.OpenForm FormName:="frmProjects"
    End If

Exit_cmdViewProjects_Click:
    Exit Sub
Err_cmdViewProjects_Click:
    MsgBox Err.Description
    Resume Exit_cmdViewProjects_Click
End Sub

The routine first invokes error handling (discussed in Chapter 16, “Error Handling: Preparing for the Inevitable”); then uses the IsNull function to test whether a ClientID has been entered. The IsNull function returns a True if the value in the txtClientID control is Null. If it is, an error message is displayed. If the txtClientID control contains a non-Null value, two methods are performed on the DoCmd object.

The first method performed on the DoCmd object is the RunCommand method. This method receives the constant associated with the name of the menu command you want to execute. The use of intrinsic constants makes this code more readable, and the RunCommand method makes it much easier to call menu commands from code. The second method performed on the DoCmd object is OpenForm, which opens the frmProjects form. The RecordSource property of the frmProjects form is programmatically set to a query that only displays projects associated with the currently selected customer.

An Example of Using a Type Structure

If many parts of an application require the same information that is stored in a specific table, it would be inefficient to read the data from this table each time the application needs it. It would be much more efficient to read this data once, when the application loads, and store it in a type structure. Because it remains in memory at all times, you can efficiently retrieve it whenever needed. The type structure is defined, and a Public Type variable based on the type structure is declared in a module’s Declarations section. It looks like this:

Type CompanyInfo
    SetUpID As Long
    CompanyName As String * 50
    Address As String * 255
    City As String * 50
    StateProvince As String * 20
    PostalCode As String * 20
    Country As String * 50
    PhoneNumber As String * 30
    FaxNumber As String * 30
    DefaultPaymentTerms As String * 255
    DefaultInvoiceDescription As String
End Type
Public typCompanyInfo As CompanyInfo

You must build a subroutine that is invoked when your startup form is first loaded. This routine populates all the elements of the type structure. The routine looks like this:

Sub GetCompanyInfo()

    Dim strSubName As String
    Dim rst As ADODB.Recordset

    'Instantiate and open a recordset
    'based on the tblCompanyInfo table
    Set rst = New ADODB.Recordset
    rst.ActiveConnection = CurrentProject.Connection
    rst.Open "Select * from tblCompanyInfo", Options:=adCmdText

    'Populate the elements of the type structure
    'with data from the table
    With typCompanyInfo
        .SetUpID = rst!SetUpID
        .CompanyName = rst!CompanyName
        .Address = rst!Address
        .City = rst!City
        .StateProvince = rst!StateOrProvince
        .PostalCode = rst!PostalCode
        .Country = rst!Country
        .PhoneNumber = rst!PhoneNumber
        .FaxNumber = rst!PhoneNumber
    End With

    'Close the recordset and destoy the object
    rst.Close
    Set rst = Nothing
End Sub

Don’t be concerned with the recordset handling included in this routine. Instead, notice that the value from each field in the first (and only) record of the tblCompanyInfo table is being loaded into the elements of the Global Type variable. Here’s an example of how the Type variable is used:

Sub PopulateControls()
    'Populate the text boxes on the report
    'with data from the type structure
    txtCompanyName.Value = Trim(typCompanyInfo.CompanyName)
    txtAddress.Value = Trim(typCompanyInfo.Address)
    txtCityStateZip.Value = Trim(typCompanyInfo.City) & ", " & _
        Trim(typCompanyInfo.StateProvince) & _
    " " & Format(Trim(typCompanyInfo.PostalCode), "!&&&&&-&&&&")
txtPhoneFax.Value = "PHONE: " & _
    Format(Trim(typCompanyInfo.PhoneNumber), "(&&&)&&&-&&&&") & _
    "       FAX: " & _
    Format(Trim(typCompanyInfo.FaxNumber), "(&&&)&&&-&&&&")
End Sub

This routine is populates four different controls on a form with the company information retrieved from the elements of the Global Type variable.

Summary

As an Access developer, you spend much of your time writing VBA code. Knowing the tricks and tips of the trade and understanding the more advanced aspects of the language will save you much time and help you streamline your application code.

This chapter showed you tricks and tips you can use to effectively navigate the VBA environment. It delved into more advanced aspects of the VBA language, such as user-defined types, constants, and arrays. You have seen the important difference between passing parameters by reference and passing them by value, and learned about other advanced function techniques, such as optional and named parameters. Other important topics covered in this chapter included collections, Empty versus Null, and compilation options. Understanding these valuable aspects of the VBA language will help you get the most out of the code you write.

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

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