Microsoft Access VBA Tutorials for self-paced learning, Class Modules, SQL Techniques, and AI Integration Guides.

MS-Access Class Module and VBA

MS Access Class Module and VBA.

    Over the past few weeks, we explored how to work with User-Defined Types (UDTs) by creating complex data structures and gained a fair understanding of their strengths and limitations. If you haven’t reviewed those articles yet, I recommend visiting them through the links below:

  • User-Defined Data Type-2
  • User-Defined Data Type-3

In Microsoft Access, there are two types of VBA Modules.

  1. Standard Modules
  2. Class Modules

We have worked with Class Modules in Forms and Reports in Microsoft Access. These modules typically contain small, event-driven routines—for example, handling button clicks, the BeforeUpdate event, or the Form/Report Current event.

For more complex data-processing tasks, the code is usually placed in Standard Modules. A single Standard Module can contain several unrelated procedures and functions to perform different operations.

Class Modules, however, are different. They are designed to define and manage custom objects, and each Class Module is dedicated to a single object.


The Basics of a Stand-alone Class Module.

  1. Let’s create a simple Class Module from scratch to understand the basics.

    1. Start Microsoft Access and open an existing database, or create a new one.

      • If you create a new database, save it in an existing Trusted Location (folder), or add the folder to your list of trusted locations.

      • To add a trusted location:

        • Click the Office Button → Access Options → Trust Center → Trust Center Settings.

        • Add your database folder to the list and click OK.

    2. Open the VBA Editor by pressing Alt + F11.

    3. In the VBA window, click on the Insert menu and select Class Module.

      • A new Class Module will be added to your project, ready for use.

  2. In the left panel, locate the Class Modules folder icon. Beneath it, you will see a class module named Class1. 

  3. Click on Class1, then open the Properties window from the View menu. Change the name from Class1 to ClsArea—this will be the name of our custom object.

Note: The class name ClsArea becomes the object type. This means that whenever we use this object, we can declare it like a regular variable:

Dim xyz As ClsArea

This is similar to how we declare variables for user-defined data types.

Next, we will add three properties (variables) to the object: Description, Length, and Width. These should be placed at the top of the class module, immediately below the Option Compare Database  Option Explicit Lines. Enter the following lines into the class module:

Option Compare Database
Option Explicit

Public p_Desc as String
Public p_Length as Double
Public p_Width as Double

These variables are recognized as properties of the VBA custom class object and are declared in the global declaration area with Public scope. Save the class module.

Note: Our class module and its code will not remain this simple. Over time, it will need more lines of code. It is important to understand each step and the purpose behind each change. At this stage, we are creating a simple class for area calculation Area = Length * Width. Gradually, we will enhance it, and you will learn why each modification is necessary.

Next, we will write a small program in a standard module to test our new class module. Insert a standard module via the Insert menu, then type or copy and paste the following code into the module, replacing any existing content:

Option Compare Database
Option Explicit

Public Function ClassTest1()
Dim oArea As ClsArea

Set oArea = New ClsArea

oArea.

Set oArea = Nothing

End Function 

The Dim statement is used to declare an object variable, similar to how we declare a regular variable—for example:

Dim Desc As String

However, in this case, we are not declaring an ordinary variable, like String or Integer. We are declaring an object variable oArea of our class ClsArea. Because it is an object, a simple Dim statement alone is not sufficient; it does not allocate memory for the object or its properties.

The member variables we previously declared in the ClsArea class are known as properties of the ClsArea object. To use these properties, the object itself must be loaded (instantiated) into memory. 

The Set statement, combined with the New keyword, is required to create an instance of the ClsArea Class in memory, referenced by the object variable oArea. With these, we allocate memory and make the object ready for use.

Multiple instances of the same class can be created in this way, each independent of the others. (We will explore this in detail later.) Once instantiated, we can store values in its properties—p_Desc, p_Length, and p_Width.

 p_ The prefix is used here to indicate that these variables are intended as private members of the class (i.e., not visible outside the class module if declared with the Private keyword). However, in our current example, they are declared as Public, and accessible outside the class. The actual variable names can be any valid identifiers; the prefix is simply a naming convention for clarity.

Note: At this stage, we have not yet declared the variables as Private. We will make that change later as the class evolves.

To create an object instance, use the Set statement. Immediately after the keyword 'Set', specify the local object name (you may choose any valid variable name, following standard naming rules), then an equal sign, and the keyword 'New' and finally the class module name (ClsArea). This creates an instance of the ClsArea object in memory, complete with all its properties (variables).

There is also a shortcut. The two lines of code required for declaration and instantiation can be combined into a single statement, as shown below:

Private oArea As ClsArea
Set oArea = New ClsArea

'the shortcut to the above two statements
Private oArea As New ClsArea

When you type the next line of oArea, followed by a dot (.) separator, the following display will appear, showing the list of available Custom Object Properties to select from and assign appropriate values to them.

If the list of properties and methods does not appear as shown above, open the Options dialog from the Tools menu and, on the Edit tab, enable the Auto List Members option.

Before exiting the function, always include the statement:

Set oArea = Nothing

This explicitly releases the memory allocated to the ClsArea object, making it available for other uses.

All operations involving the instantiated custom object should be written between the first Set statement (where the object is created) and the final Set ... = Nothing statement (where the object is released).


The ClsArea Class Object Test Program.

The completed Class Testing Program Code is given below:

Option Compare Database
Option Explicit

Public Function ClassTest1()
Dim oArea As ClsArea

Set oArea = New ClsArea

oArea.p_Desc = "Carpet"
oArea.p_Length = 25
oArea.p_Width = 15

Debug.Print "Description", "Length", "Width"

Debug.Print oArea.p_Desc, oArea.p_Length, oArea.p_Width

Set oArea = Nothing

End Function

Click anywhere in the middle of the Code and press F5 to run the program. The program output is shown below for reference.

Description   Length        Width
Carpet         25            15 

Public|Private Scope of Object Properties.

Our simple class module object has a few drawbacks that we need to address now.

The first issue is that all variables (properties) are declared with Public scope. This makes them directly accessible from other VBA programs, meaning their values can be changed freely—something we do not want.

The second issue is the lack of validation. Currently, the class accepts any values, including invalid ones such as negative or zero. The entered values should undergo validation checks before they are assigned to these variables.

The first problem can be solved easily by changing the variable declarations from Public to Private. However, once the Private scope is declared, we don't have direct access to assign values. Instead, we need to define some temporary properties through which we can assign and retrieve the actual values from the Main Variables declared with Private Scope. This is precisely what Property procedures—Get and Let—are designed for. We will create these procedures for each property of the object.

Let’s now make these changes in the class module.

Open the Class Module ClsArea. Change the Public scope to Private for all three variables.

Creating Property Procedures

Select Procedure from the Insert Menu, type strDesc in the Name text control, select Property in the Type Option Group, and Public in the Scope option group.  Click OK to insert the Property Procedures for the Private p_Desc Variable (Property).

Option Compare Database
Option Explicit

Private p_Desc As String
Private p_Length As Double
Private p_Width As Double

Public Property Get strDesc() As String
   strDesc = p_Desc 'return the value from p_Desc
End Property

Public Property Let strDesc(ByVal strNewValue As String)
   p_Desc = strNewValue ‘assign the value into p_Desc
End Property

Both the Get and Let procedures are declared as Public Property procedures, and both use the same name: strDesc. By default, the Get procedure returns a Variant type, and the parameter in the Let procedure is also created as a Variant. However, we can (and should) change these to more specific data types as needed. In our case, we changed them to the String type.

The naming convention also helps: the str prefix in strDesc indicates that this property expects a String value. Likewise, we should rename the parameter variable in the Let procedure from vNewValue to strNewValue for clarity.

When you select Properties from the Insert menu, Access inserts both the Get and Let Property procedures as a pair.

Now, let’s take a closer look at the code. In the Get procedure, the procedure name (strDesc) acts like a variable on the left side of the equals sign.=)'. It returns the value of the private variable p_Desc to the calling program.

In the Let procedure, strDesc accepts a String value through its parameter (strNewValue). The value passed is then stored in the private property p_Desc.

The key point here is that there is no direct access to our private property 'p_Desc' from outside the class. All data transfer to and from p_Desc must go through the Get/Let procedures. The Let Property Procedure can perform validation checks before assigning values to private Properties (We will add these checks shortly).

Finally, remember that the Get or Let procedure is executed automatically, depending on whether you are reading from or writing to the property in your VBA code.

The Get procedure is executed when we use the Property Name in an expression in the following manner:

‘ Reads the value from p_Desc to Print

Debug.Print oArea.strDesc 'Property Get strDesc() is called

OR

‘ Reads the value from p_Desc and assigns it to the variable X

X = oArea.strDesc 'Property Get strDesc() is called for the value

The Let Property Procedure is run when we try to assign a value to the Property Name. Check the example expression in our Test Program below:

oArea.strDesc = “Carpet” 'Property Let strDesc() is executed

In earlier BASIC Language books, you can see the usage of the keyword LET.

LET X = 25 ‘ LET is optional

Since it was optional, the statement works without it, and I stopped using it.

If you are only reading a value from a variable and not storing a value, you can omit the Let Property Procedure and use only the Get Property Procedure.

This rule applies to the Let Property Procedure.  You may use only the Let Procedure when assigning a value to a Private variable. If not reading the value from outside, then omit the Get Property Procedure.

The Get and Let Property Procedures will run one after the other if our expression is something like the following:

oArea.strDesc = oArea.strDesc & “ – King Size.”

In the above expression, to get the existing value from the Private Variable p_Desc, modify the description, and store it back into the same variable.  In short, in an expression, if you use the Property name to the right of the equal sign (=), the Get Property Procedure is called, and the Let Property Procedure is run when the object property procedure name appears to the left of the equal (=) sign.

The Let and Get Property Procedures should use the same Procedure Name:

Public Property Get strDesc() As String
   strDesc = p_Desc 'return the value from p_Desc
End Property

Public Property Let strDesc(ByVal strNewValue As String)
   p_Desc = strNewValue ‘store the value into p_Desc
End Property

Next, insert two additional sets of Property procedures for the variables p_Length and p_Width.

When assigning the procedure names in the Name control, use dblLength and dblWidth. The dbl prefix indicates to the user that these properties expect Double-precision numbers as input.

ClsArea Class Object with its Property Procedures.

The completed code so far, the dblLength and dblWidth Property Procedures, is given below for reference and to update your code.

Option Compare Database
Option Explicit

Private p_Desc As String
Private p_Length As Double
Private p_Width As Double

Public Property Get strDesc() As String
  strDesc = p_Desc 'copy the value from p_Desc
End Property

Public Property Let strDesc(ByVal strNewValue As String)
  p_Desc = strNewValue
End Property

Public Property Get dblLength() As Double
  dblLength = p_Length
End Property

Public Property Let dblLength(ByVal dblNewValue As Double)
  p_Length = dblNewValue
End Property

Public Property Get dblWidth() As Double
  dblWidth = p_Width
End Property

Public Property Let dblWidth(ByVal dblNewValue As Double)
  p_Width = dblNewValue
End Property

The Test Program with the Changes.

If you have completed updating the code, let us make some changes to our Test program to view the changes made here.  The modified sample code is given below.

Option Compare Database
Option Explicit

Public Function ClassTest1()
Dim oArea As ClsArea

Set oArea = New ClsArea

‘Property Let procedures called here

oArea.strDesc = "Carpet"
oArea.dblLength = 25
oArea.dblWidth = 15

Debug.Print "Description", "Length", "Width"

‘Property Get Procedures called here to print
Debug.Print oArea.strDesc, oArea.dblLength, oArea.dblWidth

Set oArea = Nothing

End Function

When you type a dot (.) immediately after the object name—for example, oArea.—VBA IntelliSense displays a list of the available property and procedure names. From this list, you can simply select the required property instead of typing it manually.

The purpose of our class object is to calculate the area of something—such as a room, carpet, floor tile, or any other item that has measurable length and width. To achieve this, we need to add a Public Function to the class. This function will calculate the area based on the Length and Width values entered into the class object, while also using the descriptor value for context.

ClsArea Object Method: Area()

Here is the Code for the Public Function:

Public Function Area() As Double
   Area = Me.dblLength * Me.dblWidth
End Function

You can insert this function into the ClsArea class module using the Insert menu. In the Name control, enter Area; select Function in the Type option group; and choose Public as the scope. Then, complete the function by adding the calculation line inside it.

Since the Area() function resides within the class module, we could directly reference the private variables p_Length and p_Width in the calculation. However, we will take the proper approach and use the corresponding Get procedures (dblLength and dblWidth).

Notice the use of the keyword Me (as in Me.dblLength and Me.dblWidth). Just like in Form or Report class modules, 'Me' refers to the current object instance in memory. Because multiple instances of our custom class object can exist simultaneously, 'Me' ensures that the function works with the correct instance of the object.

The Test Function with Modification.

Modify our Test Function ClassTest1() to incorporate the Area() function output as below:

Option Compare Database
Option Explicit

Public Function ClassTest1()
Dim oArea As ClsArea

Set oArea = New ClsArea

oArea.strDesc = "Carpet"
oArea.dblLength = 25
oArea.dblWidth = 15

Debug.Print "Description", "Length", "Width", "Area"
Debug.Print oArea.strDesc, oArea.dblLength, oArea.dblWidth, oArea.Area

Set oArea = Nothing

End Function

The change is only in the Debug.Print statements.  Run the code and check the Debug Window for the result.

There are two auto-executing subroutines (if present) in the Class Object Modules.

  1. Class_Initialize()

  2. Class_Terminate()

Auto-Executing Methods.

The Class_Initialize() procedure is executed automatically whenever an object is instantiated with the New keyword. This procedure is often used to assign default values to variables or to instantiate other objects in memory. A class object can also make use of other classes as child objects, which may need to be instantiated during initialization. We will explore this aspect in more detail later.

The Class_Terminate() procedure is similar to the Form_Unload() event procedure. It runs automatically before the class object is closed. Normally, the statement Set oArea = Nothing is sufficient to explicitly release a class object from memory.

As a good programming practice, always include it Set oArea = Nothing as the final executable statement in your procedures. This ensures that the object is cleared from memory and the system resources are released.

We will add the programs to our Class Module.  Add the following code at the end of your Class Module:

Private Sub Class_Initialize()
   p_Length = 0
   p_Width = 0

   'MsgBox "Initialize.", vbInformation, "Class_Initialize()"
End Sub

Private Sub Class_Terminate()
   'MsgBox "Terminate.", vbInformation, "Class_Terminate()"
End Sub

If you would like to test these two subroutines, then remove the Comment symbol and make the MsgBox active.  Run your test program one more time.  You will find the Initialize message appears at the beginning (Click OK to continue)  and the Terminate message appears at the end of the Test Program.

I know what you have in your mind by now, like “So much code for multiplying two variables together”.  It is true from that perspective, but we will be writing problem-solving code repeatedly, duplicating code for validation checks and for other logical error safeguards.

At this stage, we are not simply writing an ordinary program—we are developing a custom object. Once created, this object can be reused multiple times or embedded in other objects, wherever needed, without the user having to worry about its internal workings.

This is the same principle behind Microsoft Access’s many built-in objects and functions. We use them every day—by setting their properties or passing parameters—to get work done, without needing to know how they are implemented behind the scenes. Our custom class object follows the same approach.

There is one more issue we need to address: adding validation checks to the values passed into the dblNewValue parameters of the Let property procedures for dblLength() and dblWidth(). These checks are to ensure that only valid values are assigned to the object properties p_Length and p_Width.

Specifically, negative or zero values should be treated as invalid. To prevent this, we must add safeguards to ensure only valid, positive values are assigned to the object properties before any calculations are performed.

Performing Validation Checks.

The modified Let Property Procedure Code segments are given below.  Make changes in your code accordingly.

Public Property Let dblLength(ByVal dblNewValue As Double)
   Do While dblNewValue <= 0
      dblNewValue = InputBox("Negative/0 Values Invalid:", "dblLength()", 0)
   Loop

   p_Length = dblNewValue
End Property


Public Property Let dblWidth(ByVal dblNewValue As Double)
   Do While dblNewValue <= 0
       dblNewValue = InputBox("Negative/0 Values Invalid:", "dblwidth()", 0)
   Loop

   p_Width = dblNewValue
End Property

The Do While. . . Loop runs repeatedly till a valid value (greater than 0) is entered into dblNewValue by the User

Validation Checks in the Public Method: Area()

We need one more validation check in the Area() Function.  If the user calls the Area() function without entering valid values for Length and Width, the User must be informed.  We will check whether the p_Length and p_Width variables have valid values before running the area calculation.  Here is the Code:

Public Function Area() As Double

   If (p_Length > 0) And (p_Width > 0) Then
       Area = p_Length * p_Width
   Else
       Area = 0
       MsgBox "Error: Length/Width Value(s) Invalid., Program aborted."
   End If

End Function

The Complete Code of the ClsArea Object.

The fully completed Code of our Class Module ClsArea is given below:

Option Compare Database
Option Explicit

Private p_Desc As String
Private p_Length As Double
Private p_Width As Double

Public Property Get strDesc() As String
    strDesc = p_Desc 'copy the value from p_Desc
End Property

Public Property Let strDesc(ByVal strNewValue As String)
    p_Desc = strNewValue
End Property

Public Property Get dblLength() As Double
   dblLength = p_Length
End Property

Public Property Let dblLength(ByVal dblNewValue As Double)
    Do While dblNewValue <= 0
       dblNewValue = InputBox("Negative/0 Values Invalid:", "dblLength()", 0)
    Loop

    p_Length = dblNewValue
End Property

Public Property Get dblWidth() As Double
   dblWidth = p_Width
End Property

Public Property Let dblWidth(ByVal dblNewValue As Double)
    Do While dblNewValue <= 0
       dblNewValue = InputBox("Negative/0 Values Invalid:", "dblwidth()", 0)
    Loop

    p_Width = dblNewValue
End Property

Public Function Area() As Double

   If (p_Length > 0) And (p_Width > 0) Then
       Area = p_Length * p_Width
   Else
       Area = 0
       MsgBox "Error: Length/Width Value(s) Invalid., Program aborted."
   End If

End Function

Private Sub Class_Initialize()
   p_Length = 0
   p_Width = 0
End Sub

Private Sub Class_Terminate()
   'Exit
End Sub

Testing Property Procedures and Methods.

You may test our Custom Class Object by entering negative or 0 values as input to the dblLength and dblWidth Properties. 

In the Test Program, comment out the lines (oArea.dblLength=25 and oArea.dblWidth=15) to test the Area() function. It should display the Error message we have written in the function.

Our Area Calculation Class Object is now considered complete, and we have tested and found it working correctly, with the following VBA Function:

Public Function ClassTest2()
Dim oArea As ClsArea

Set oArea = New ClsArea

oArea.strDesc = "Carpet"
oArea.dblLength = 0
oArea.dblWidth = 15

Debug.Print "Description", "Length", "Width", "Area"
Debug.Print oArea.strDesc, oArea.dblLength, oArea.dblWidth, oArea.Area

Set oArea = Nothing

End Function

You may test it further for any logical errors that I have overlooked. If you come across anything that I didn't anticipate, please share it with me.

Future Plan for Testing.

So far, we have tested the class object with only a single item. In practice, however, we often need to calculate the area for multiple items—for example, five bedrooms or ten carpets of different sizes. Once a class object is developed, it can be instantiated multiple times in memory, with each instance holding its own unique set of values. This makes it possible to instantiate the same object multiple times, while keeping the values in each instance completely independent.

In addition, this object can serve as a building block for other objects we develop. By reusing the functionality already implemented in the ClsArea class module, we need only minimal code to make our solutions both simpler to maintain and more efficient to build.

Next week, we will learn how to create an Array of Custom Objects to calculate the area of several items.

  1. MS-Access Class Module and VBA
  2. MS-Access VBA Class Object Arrays
  3. MS-Access Base Class and Derived Objects
  4. VBA Base Class and Derived Objects-2
  5. Base Class and Derived Object Variants
  6. MS-Access Recordset and Class Module
  7. Access Class Module and Wrapper Classes
  8. Wrapper Class Functionality Transformation
  9. MS Access and Collection Object Basics
  10. MS-Access Class Module and Collection Object
  11. Table Records in Collection Object and Form
  12. Dictionary Object Basics
  13. Dictionary Object Basics-2
  14. Sorting Dictionary Object Keys and Items
  15. Display Records from Dictionary to Form
  16. Add Class Objects as Dictionary Items
  17. Update Class Object Dictionary Item on Form

Share:

User-Defined Data Type-3

User-Defined Data Type 3.

Last week, we learned how to define a User-Defined Data Type (UDT) and how to use it in programs. If you’ve just landed on this page, it’s recommended that you first examine the earlier post “User-Defined Data Type–2” before proceeding.

A user-defined type is declared within the [Private/Public] Type… End Type structure. Immediately after the Type keyword, you assign a name to the data type. The actual data-holding variables are defined as individual elements inside the structure, typically using built-in variable types such as String, Integer, Long, Double, or Variant.

In addition to built-in variable types, you can also use other user-defined types as elements. These act as child elements within a user-defined type — and that is what we explore here.

Combining Different Sets of User-Defined Types.

First, we will declare several User-Defined Types (UDTs) to represent different categories of information for employee records — such as Home Address, Qualification, and Experience.

Each of these groups will be defined separately, with its own related data elements. Later, we will combine these groups, along with other individual fields, under a single UDT named Employee.

The following layout shows how these individual groups of employee information are structured separately, before being organized together under the common Employee user-defined type.


1.Qualification
Desc1
Desc2
2.Experience
Desc1
Desc2
3.Address and Date of Birth
Address1
Address2
City
State
PIN
BirthDate
4.Employee Details
Employee Name
Designation
Join Date
Monthly Performance Score(1 to 12)
Salary

Declaring the Data Types

First, let us declare the Data Types:

Option Compare Database

Public Type Qualification
    Q_Desc1 As String
    Q_Desc2 As String
End Type

Public Type Experience
     X_Desc1 As String
     X_Desc2 As String
End Type

Public Type BioData
    Address1 As String
    Address2 As String
    City As String
    State As String
    PIN As String
    BirthDate As Date
End Type

Public Type Employee
        E_Name As String * 45
        E_Desig As String * 25
        E_JoinDate As Date
        E_PerfScore(1 To 12) As Double
        E_Salary As Double
End Type

BioData Redefined with Qualification and Experience.

The Qualification and Experience user-defined types are now incorporated as child elements within the BioData type.

After this modification, the BioData type structure is similar to the code shown below.

Public Type BioData Address1 As String Address2 As String City As String State As String PIN As String BirthDate As Date Q_Desc As Qualification X_Exp As Experience End Type

The Qualification user-defined type contains two String data types. Similarly, the Experience type also includes two String elements.

The BioData type consists of address details and date of birth as its own elements. In addition, the Qualification and Experience types are embedded as child elements within the BioData type.

Employee Data Type combined with modified BioData Type.

We will now define the BioData data Type as a child element of the Employee data type. The BioData element will contain two sub-elements: Qualification and Experience.

The Employee Data Type with BioData Type as a child element.

Public Type Employee E_Name As String * 45 E_Desig As String * 25 E_JoinDate As Date E_PerfScore(1 To 12) As Double E_Salary As Double B_BioData As BioData

End Type

Now you can see how all these elements fit together to form a complete Employee record.

The Employee data type consists of four elements:

  • E_Name – A String type, with a maximum length of 45 characters.

  • Designation – A String type, with a maximum length of 25 characters.

  • JoinDate – A Date type field to store the joining date.

  • PerformanceScore – An array of 12 elements (numeric values) to store the employee’s monthly performance evaluation scores, recorded by the management on a scale of 10.

Note: The string length specifications (like 45 or 25) are arbitrary. You may keep them as they are, or simply declare the fields as String without specifying a length.

The Qualification and Experience data types were declared first, and then inserted as child elements of the BioData data type.

The BioData data type itself was declared above the Employee type, before being inserted into it as a child element.

Caution.

Ensure that you are not placing a Type declaration within itself, as this would create a circular reference and is not allowed.

Now that we are ready to work with this complex data structure, the first question that naturally comes to mind is:

How do we reference each element to assign values to it?

Sample Test Program for Employee Data Type.

We will now write a small program to test the Employee data type and assign values to each element of this nested complex data structure.

However, it is not as complicated as it may sound. If you find it difficult to follow, try creating simpler examples on your own based on your current level of understanding.

The program code is given below.

Observe carefully how each element is referenced when assigning values to it.

Public Function EmplTypeTest() Dim Emp As Employee Emp.E_Name = "John" Emp.E_Desig = "Manager" Emp.E_JoinDate = #01/01/2018# Emp.E_PerfScore(Month(Date) - 1) = 4.5 Emp.E_Salary = 40000 'BioData Emp.B_BioData.Address1 = "115/8" Emp.B_BioData.Address2 = "Olencrest," Emp.B_BioData.City = "Columbus" Emp.B_BioData.State = "Ohio" Emp.B_BioData.PIN = "43536" Emp.B_BioData.BirthDate = #9/29/1979# 'Qualifications Emp.B_BioData.Q_Desc.Q_Desc1 = "Degree in Computer Science" Emp.B_BioData.Q_Desc.Q_Desc2 = "PG Degree in Computer Science" 'Experience Emp.B_BioData.X_Exp.X_Desc1 = "From Jan-2010 onwards Working as Project Manager, with XYZ Company." Emp.B_BioData.X_Exp.X_Desc2 = "From Mar-2005 to Dec-2009 worked as Team Leader with ABC Enterprise."

Call ListEmp(Emp) End Function

As you can see in the code above, each element is referenced using its fully qualified name, clearly indicating its hierarchical position within the Employee data structure.

The same code is shown again below, but this time using full object references enclosed within With ... End With statements, where the XXXX part represents the hierarchical object names.

Public Function EmplTypeTest0()
Dim Emp As Employee

With Emp
  .E_Name = "John"
  .E_Desig = "Manager"
  .E_JoinDate = #01/01/2018#
  .E_PerfScore(Month(Date) - 1) = 4.5
  .E_Salary = 40000
End With

        'BioData
With Emp.B_BioData
        .Address1 = "115/8"
        .Address2 = "Olencrest,"
        .City = "Columbus"
        .State = "Ohio"
        .PIN = "43536"
        .BirthDate = #9/29/1979#
End With

       'Qualifications
With Emp.B_BioData.Q_Desc
            .Q_Desc1 = "Degree in Computer Science"
            .Q_Desc2 = "PG Degree in Computer Science"
End With

        'Experience
With Emp.B_BioData.X_Exp
            .X_Desc1 = "From Jan-2010 onwards Working as Project Manager, with XYZ Company."
            .X_Desc2 = "From Mar-2005 to Dec-2009 worked as Team Leader with ABC Enterprise."
End With

    Call ListEmp(Emp)

End Function

Referencing individual Element Variables of Employee Data Type.

Check how the With... End With statements are structured, and how the object references are given in the correct hierarchical order to reach each element variable.

In the revised version of the code shown below, the With... End With blocks are nested, and each block uses only the nearest parent object name to access its data elements.

Public Function EmplTypeTestA() Dim Emp As Employee With Emp .E_Name = "John" .E_Desig = "Manager" .E_JoinDate = #01/01/2018# .E_PerfScore(Month(Date) - 1) = 4.5 .E_Salary = 40000 'B_BioData With Emp.B_BioData .Address1 = "115/8" .Address2 = "Olencrest," .City = "Columbus" .State = "Ohio" .PIN = "43536" .BirthDate = #9/29/1979# 'Qualifications With .Q_Desc .Q_Desc1 = "Degree in Computer Science" .Q_Desc2 = "PG Degree in Computer Science" End With 'Experience With .X_Exp .X_Desc1 = "From Jan-2010 onwards Working as Project Manager, with XYZ Company." .X_Desc2 = "From Mar-2005 to Dec-2009 worked as Team Leader with ABC Enterprise." End With

End With ‘ Emp.B_BioData End With ‘ Emp Call ListEmp(Emp) End Function

You can use any of the three programs shown above—or run all of them one by one—before executing the following printing program, which will display the data in the Debug Window.

Public Function ListEmp(ByRef EmpList As Employee)
With EmpList
    Debug.Print "Name: ", , .E_Name
    Debug.Print "Designation: ", , .E_Desig
    Debug.Print "Join Date: ", , .E_JoinDate
    Debug.Print "Performance Score July: ", .E_PerfScore(8)
    Debug.Print "Salary: ", , .E_Salary
    
    Debug.Print "Address1: ", , .B_BioData.Address1
    Debug.Print "Address2: ", , .B_BioData.Address2
    Debug.Print "City: ", , .B_BioData.City
    Debug.Print "State: ", , .B_BioData.State
    Debug.Print "PIN: ", , .B_BioData.PIN
    
    Debug.Print "Qualification1: ", .B_BioData.Q_Desc.Q_Desc1
    Debug.Print "Qualification2: ", .B_BioData.Q_Desc.Q_Desc2
    
    Debug.Print "Experience1: ", , .B_BioData.X_Exp.X_Desc1
    Debug.Print "Experience2: ", , .B_BioData.X_Exp.X_Desc2
    
End With

End Function

Sample Output on the Debug Window.

Sample Output displayed on the Debug Window is given below:

Name:                       John                                         
Designation:                Manager                  
Join Date:                  01-01-2018 
Performance Score August:    4.5 
Salary:                      40000
Address1:                   115/8
Address2:                   Olencrest,
City:                       Columbus
State:                      Ohio
PIN:                        43536
Qualification1:             Degree in Computer Science
Qualification2:             PG Degree in Computer Science
Experience1:                From Jan-2010 onwards Working as Project Manager, with XYZ Company.
Experience2:                From Mar-2005 to Dec-2009 worked as Team Leader with ABC Enterprise.

Input Values through the Keyboard.

If you would like to try an array example, then copy and paste the following two programs into a Standard Module, then run the first code. 

Public Function EmplTypeTestB()
Dim Emp(1 To 3) As Employee
Dim j As Integer, strlabel As String

For j = 1 To 3

With Emp(j)
strlabel = "( " & j & " )"
    .E_Name = InputBox(strlabel & "Name:")
    .E_Desig = InputBox(strlabel & "Designation:")
    .E_JoinDate = InputBox(strlabel & "Join Date:")
    .E_PerfScore(Month(Date) - 1) = InputBox(strlabel & "Performance Score:")
    .E_Salary = InputBox(strlabel & "Salary:")
    
   'B_BioData
    With Emp(j).B_BioData
        .Address1 = InputBox(strlabel & "Address1:")
        .Address2 = InputBox(strlabel & "Address2:")
        .City = InputBox(strlabel & "City:")
        .State = InputBox(strlabel & "State:")
        .PIN = InputBox(strlabel & "PIN:")
        .BirthDate = InputBox(strlabel & "Birth Date:")
       
       'Qualifications
        With .Q_Desc
            .Q_Desc1 = InputBox(strlabel & "Qualification-1:")
            .Q_Desc2 = InputBox(strlabel & "Qualification-2:")
        End With
    
        'Experience
        With .X_Exp
            .X_Desc1 = InputBox(strlabel & "Experience-1:")
            .X_Desc2 = InputBox(strlabel & "Experience-2:")
        End With
    End With
End With
Next

    
    Call ListEmp2(Emp)

End Function

The InputBox() function asks you to enter the details of three employees directly from the keyboard when the prompt appears. In the final statement, Call ListEmp2(Emp) runs the following code using the employee records array and prints the output in the Debug Window. Make sure the Debug Window is open (Ctrl+G).

Public Function typeTest()
Dim mySales As Sales

   mySales.Desc = "iPhone 8 Plus"
   mySales.Quantity = 1
   mySales.UnitPrice = 75000#
   mySales.TotalPrice = mySales.Quantity * mySales.UnitPrice

Debug.Print mySales.Desc, mySales.Quantity, mySales.UnitPrice, mySales.TotalPrice

End Function
 

Printing Program.

Public Function ListEmp2(ByRef EmpList() As Employee)
Dim j As Integer, strlabel As String
Dim lower As Integer
Dim upper As Integer

lower = LBound(EmpList)
upper = UBound(EmpList)

For j = lower To upper
With EmpList(j)
Debug.Print
    Debug.Print "=== Employee: " & .E_Name & "  Listing ==="
    Debug.Print "Name: ", , .E_Name
    Debug.Print "Designation: ", , .E_Desig
    Debug.Print "Join Date: ", , .E_JoinDate
    Debug.Print "Performance Score " & MonthName(Month(Date) - 1) & ": ", .E_PerfScore(8)
    Debug.Print "Salary: ", , .E_Salary
    
    Debug.Print "Address1: ", , .B_BioData.Address1
    Debug.Print "Address2: ", , .B_BioData.Address2
    Debug.Print "City: ", , .B_BioData.City
    Debug.Print "State: ", , .B_BioData.State
    Debug.Print "PIN: ", , .B_BioData.PIN
    
    Debug.Print "Qualification1: ", .B_BioData.Q_Desc.Q_Desc1
    Debug.Print "Qualification2: ", .B_BioData.Q_Desc.Q_Desc2
    
    Debug.Print "Experience1: ", , .B_BioData.X_Exp.X_Desc1
    Debug.Print "Experience2: ", , .B_BioData.X_Exp.X_Desc2
    
End With

Next
End Function

I hope you will experiment with User-Defined Types (UDTs) in your projects and explore their strengths and limitations.

The main drawback of UDTs is the lack of a built-in mechanism to validate the data assigned to their elements. For example, if a future date is entered into the Date of Birth element, there is no built-in validation to alert the user that the value is invalid. Similarly, if a negative value is entered into a Salary field, it will be accepted without any error. Wherever data validation is required, you must write separate validation code each time you use a UDT.

A better approach is to use Class Modules. In a Class Module, you can define each data element as a property and include validation logic within its Set property procedures to ensure data integrity before accepting the values. You can also create Functions or Subroutines within the Class Object to handle common operations on the data, and simply call them from your programs. All of this logic remains encapsulated within the Class Module itself, so you don’t need to rewrite it separately every time.

In the next section, we will learn how to use Class Modules to define structured data and use them effectively in programs.

Share:

User Defined Data Type-2

User-Defined Data Type 2.

This is the second post on User-Defined Data Types (UDTs).

You can find the link to the first post on this topic [here].

The need for User-Defined Data Types arose when we used two-dimensional arrays of the Variant data type to store different types of values (such as String, Integer, and Double) in each array element. The Variant data Type can automatically adjust its type depending on the value assigned to it.

Instead of using a single Variant variable with a multi-dimensional array, we could also use several separate single-dimensional variables of different data types. For most simple data-processing tasks, these approaches are more than sufficient.

However, learning new techniques is always exciting in programming.

The User-Defined Data Type is one such interesting feature in VBA, and in this post, we will explore how to create and use it in our programs.

How to Define a User-Defined Data Type

The steps go something like this:

  1. You can define a new data type that combines multiple built-in variable types—such as Integer, Long, Double, and String—into a single structure. This User-Defined Data Type (UDT) must be declared within a Type ... End Type block at the module level, typically at the beginning of a Standard Module, before any procedures.

  2. The following example defines a UDT named myRecord with two elements: RecordID (of type Long) and Description (of type String):

    Public Type myRecord
    
    RecID as Long
    Description as String
    
    End Type
    

    By default, a User-Defined Type (UDT) has Public scope, though explicitly declaring it as Public or Private is optional. If declared as Private, the UDT is accessible only in the same Module where it is defined. In that case, variables such as Dim AbcRec As myRecord can be declared and used only inside that module.

    When declared with the default Public scope, the UDT becomes available to all modules within the current project, and even to other projects that reference this database.

    Let’s begin with a simple example:

    Type Sales
        Desc As String
        Quantity As Long
        UnitPrice As Double
        TotalPrice As Double
    End Type
    

    The Data Type name is Sales.

    As you can see in the Sales data type, we have used built-in data Types String, Long Integer, and Double for different data elements.

    Using the User-Defined variable in the program starts with dimensioning a Variable of Type Sales, like any other variable.   

    Public Function typeTest()
    Dim mySales As Sales
    
       mySales.Desc = "iPhone 8 Plus"
       mySales.Quantity = 1
       mySales.UnitPrice = 75000#
       mySales.TotalPrice = mySales.Quantity * mySales.UnitPrice
    
    Debug.Print mySales.Desc, mySales.Quantity, mySales.UnitPrice, mySales.TotalPrice
    
    End Function
     

    Result printed in the Debug Window:

    iPhone 8 Plus  1             75000         75000 
    

    Unlike built-in variables, the elements of a User-Defined Type are always accessed by specifying the Type name, followed by a dot, and then the element name (for example: mySales.Desc). In this way, the Desc, Quantity, UnitPrice, and TotalPrice elements are treated as individual properties of mySales.

    To make the code cleaner and more flexible, we can place these element references inside a With… End With structure. This allows entering data directly from the keyboard into each element of the Sales record using the InputBox() function.


Public Function typeTest()
Dim mySales As Sales

With mySales
   .Desc = InputBox("Item Description: ")
   .Quantity = InputBox("Item Quantity: ")
   .UnitPrice = InputBox("Item Unit Price: ")
   .TotalPrice = .Quantity * .UnitPrice
End With

'Print the values on Debug Window
With mySales
  Debug.Print .Desc, .Quantity, .UnitPrice, .TotalPrice
End With
End Function

The modified code retrieves information from a single Record and prints it on the Debug Window.  Before running the Code, open the Debug Window (Ctrl+G) to view the output.

Arrays of User-Defined Type

Next, we will define an array of mySales variables to store information for five different items. We will then pass this array of user-defined variables—each containing multiple data elements—to the SalesPrint() function as a parameter. The SalesPrint() function will calculate and update the TotalPrice element for each item before printing the array values in the Debug Window.
(Keep the Debug Window open while running the program.)

The sample VBA code for this program is given below:

Public Function SalesRecord()
Dim mySales(5) As Sales
Dim j As Integer, strLabel As String

For j = 0 To UBound(mySales) - 1
    strLabel = "(" & j + 1 & ") "
    With mySales(j)
       .Desc = InputBox(strLabel & "Item Description:")
       .Quantity = InputBox(strLabel & "Quantity:")
       .UnitPrice = InputBox(strLabel & "UnitPrice:")
       .TotalPrice = 0
    End With
Next

Call SalesPrint(mySales())

End Function

Check the Dim statement—it works like any other array definition. We also declare two additional variables: j and strLabel. The variable j serves as the control variable in the For…Next loop, while strLabel is used to construct and store a label such as (1), (2), etc., which appears in the InputBox() prompt. This label helps to identify the current record number as we enter data into each record.

We have chosen meaningful names for the array elements—Desc, Quantity, and UnitPrice—instead of using array indexes such as Sales(0,0) for Description or Sales(0,1) for Quantity. The 'MySales(j).TotalPrice' element is initially assigned 0; its value will be calculated and updated in the SalesPrint() function. The array is passed to the SalesPrint() function as a ByRef parameter.

The SalesPrint() Function.

The SalesPrint() function Code is given below:

Public Function SalesPrint(ByRef PSales() As Sales) Dim j As Integer, strLabel As String Debug.Print "Description", " ", "Quantity", "UnitPrice", "Total Price" For j = 0 To UBound(PSales) - 1 strLabel = "(" & j + 1 & ") " With PSales(j)

'calculate TotalPrice

.TotalPrice = .Quantity * .UnitPrice 'print the values in debug window Debug.Print strLabel & .Desc, " ", .Quantity, .UnitPrice, .TotalPrice End With Next End Function

The SalesPrint() function receives the Sales Record array by reference through the PSales variable. Within the function, two local variables are defined: j as an Integer and strLabel as a String. The function begins by printing a record Field Column heading line in the debug window.

When printing the data, commas are used to separate each item, placing them in 14-column zones on the same line. To allow the Item Description to occupy more space, an empty string is inserted as the second item on the print line. This effectively moves the Quantity value to the 28th column while keeping the layout neat and aligned.

Next, the For... Next loop to access each record in memory, with the control variable j serving as the array index. The loop runs from 0 to 4, processing all five records.

The first line inside the loop constructs a label in the format (1), (2), and so on, to indicate the sequence of records as they were entered using the InputBox() function.

The next statement uses a With... End With structure on the PSales(j) record. This allows direct access to its elements (Desc, Quantity, etc.) without repeatedly referencing the top-level array name.

Within the With block, the TotalPrice value is calculated and assigned. The following line then prints the current record to the Debug Window. This process repeats for each record, ultimately displaying all items in the array.

By now, you should have a good understanding of the usefulness of User-Defined Types. With a little further exploration, you can even save these records from memory into an Access table.

Keep in mind that a User-Defined Type is usually designed for a specific task, and its structure may not be suitable for general-purpose use like built-in variables. For example, elements such as Desc, Quantity, UnitPrice and so on, may not be relevant outside the context for which the type was created.

There are, however, many interesting ways to leverage User-Defined Types, and we will continue this discussion in next week’s post.

Share:

Passing Two Dimensional Array to Function

Passing a Two-Dimensional Array Function.

First of all, I have some good news for you: our website, LEARN MS-ACCESS TIPS AND TRICKS, has been selected by the https://blog.feedspot.com panel as one of the Top 5 Microsoft Access Blogs on the Web and awarded the Badge given below.

Microsoft Access Blogs


You can find some of the top-ranked blogs on a variety of subjects, RSS feeds, YouTube channels, top news websites, and other sources. Subscribe to blogs, news feeds, or topics in any other area of interest, and receive regular updates from www.blog.feedspot.com in your Inbox as they become available by providing your email address.

Returning to our VBA lessons, last week we briefly discussed passing arrays as parameters to a Function using the ByRef method. This allowed us to work directly with the original array inside the called function and sort its values in descending order.

In our example, we used array values for only five elements, but in practice, an array can hold many rows and columns of data.

The Re-Dimension (ReDim) Statement.

An array can be re-dimensioned multiple times during program execution to increase or decrease the number of rows if its size cannot be determined in advance. In such cases, you should omit the element count in the initial Dim statement.

Example:

'Cannot Re-dimension pre-defined Arrays
.
Dim Products(1 to 5) as String
.
or
.
Dim Products(5) as String'The number of elements are predefined

'Re-dimension this Array later for required  
'Number of elements, not known in advance. 
.
Dim Products() as String
.
'Re-Dimension the Array for required number of elements
'Remember the array index numbers will be 0 to 4, total 5 elements
ReDim Products(5) As String
'
'OR
'In this case Array Index Number Range 1 to 5
ReDim Product(1 to 5) As String

'later on in the program
'all the values assigned to first 5 elements will be lost.
.
ReDim Products(7) As String 
.
or
.
ReDim Products(Ubound(Products)+2) As String
.
'To preserve the values already assigned to first 5 elements
.
ReDim Preserve Products(7) As String

Note: The important point is that the array re-dimensioning should be done in the calling procedure itself—before passing it to the called function if a size change is required. Although arrays are passed by reference and the called function can work directly with the passed variable, it cannot re-dimension the array to increase or decrease its number of elements from within the called function.

Two-Dimensional Array as Function Argument.

Now that we are aware of the limitations of passing arrays to a called function, let’s try passing a two-dimensional array containing Sales data to a function and printing its values in the Debug Window. Each record in the Sales data array will contain the following values:
  1. Product Name - Text

  2. Quantity  - Integer

  3. Unit Price – Double

  4. Total Value  - Double (will be calculated in the called function)

The sales records contain fields with different data types. Normally, to pass these values individually to a called function, we would need four separate array variables with different data types — for example:

  1. String for Product Name

  2. Integer for Quantity

  3. Double for Unit Price

  4. Double for Total Price

Each field’s values would be stored in its own array variable and passed separately to the function.

However, we will do this differently. Instead of using four separate arrays, we will use a single variable: a two-dimensional array of the Variant data type, with four rows (each row representing one sales record) and four columns (each column representing a field). This way, we can pass all four fields of each record together as a single variable to the called function.

The Variant Data Type.

We are not storing the column names anywhere within the array. It is simply assumed that:

  • The first column holds the Product Name

  • The second column holds the Quantity

  • The third column holds the Unit Price

  • The fourth column holds the Total Price

Since we are using a Variant data type for the array, each array element can hold different data types. A Variant variable automatically adapts its data type to match the type of value assigned to it. This flexibility allows us to store text, integers, and floating-point numbers together in the same array.

Before writing the complete function, let’s first examine how the Dim statement defines the two-dimensional array and how the sales values are assigned to each array element.

' variant Variable can hold different data types in each element 
Dim Product(4,4) as Variant 
'
Product(0, 0) = "Hard Disk": Product(0, 1) = 5: Product(0, 2) = 125.5: Product(0, 3) = 0

Product(1, 0) = "Keyboard": Product(1, 1) = 2: Product(1, 2) = 25.25: Product(1, 3) = 0

Product(2, 0) = "Mouse": Product(2, 1) = 3: Product(2, 2) = 13.75: Product(2, 3) = 0

Product(3, 0) = "DVD Writer": Product(3, 1) = 10: Product(3, 2) = 30: Product(3, 3) = 0

In the above example, we have only four records (rows) in our table, and each record contains four fields (columns). Every cell in this two-dimensional array is identified by a pair of numbers—the row index and the column index—separated by a comma. The index number on the left represents the row index, and the number on the right represents the column index. Since our array has 4 rows and 4 columns, both index ranges go from 0 to 3.

Here is how the columns are arranged:

  • Column 0: Product Name

  • Column 1: Quantity

  • Column 2: Unit Price

  • Column 3: Total Value (to be calculated and assigned later)

We can pass the entire array to a function as a ByRef parameter, allowing the function to work directly with the original data.

If you are new to two-dimensional arrays, it can feel a bit confusing at first to understand how the values are arranged and how to refer to each cell. This becomes even trickier when performing calculations across multiple cells within the same row.

Fortunately, there is a better way to handle this complexity—by using User-Defined Variables. Yes, you can actually define your own variable type, in addition to the built-in types provided by VBA.

We will explore this concept in detail next week. Once you get familiar with it, you’ll find it much easier and more intuitive than juggling rows and columns. The best part is that this method scales effortlessly—you can work with 5 rows, 500 rows, or even 5000 rows using the same statements in your function.

Create the Product List Data.

Public Function ProdList()
Dim Products(4, 4) As Variant
Dim j As Integer, k As Integer, stridx As String
' 0 = Description
' 1 = Quantity
' 2 = Unit Price
' 3 = Total Price to be calculated
'Array elements index numbers are 0 to 3
For j = 0 To 3
 For k = 0 To 3
    stridx = "(" & j & "," & k & ")"
    Select Case k
        Case 0
          Products(j, k) = InputBox("Product Name" & stridx)
        Case 1
          Products(j, k) = InputBox("Quantity" & stridx)
        Case 2
          Products(j, k) = InputBox("Unit Price" & stridx)
        Case 3
          Products(j, k) = 0 'total value will be calculated
    End Select
    Next k
Next j

Call ProdPrint(Products)

End Function

VBA Code Line by Line

We have defined the Products variable as a Variant data type with 4 rows and 4 columns, so it can hold values of different data types in each cell.

In the next line, we declare three more variables:

  • j and k as control variables for the For…Next loops

  • strIdx as a String variable for building text to display the index numbers of each cell in the InputBox() prompt.

We then set up two nested 'For … Next' loops to control the index row and column numbers. The outer loop (j) controls the row index, while the inner loop (k) controls the column index.

Inside the inner loop, we use a Select Case…End Select structure to determine which field is being processed based on the current value of k:

  • When k = 0, the InputBox() function prompts for the Product Name and assigns it to Products(j, 0).

  • When k = 1, it prompts for the Quantity and assigns the value to Products(j, 1).

  • When k = 2, it prompts for the Unit Price and assigns it to Products(j, 2).

  • When k = 3, it initializes Products(j, 3) = 0 (this will later hold the calculated Total Price).

The outer loop repeats this process four times (for j = 0 to 3). For each row, the inner loop repeats four times (for k = 0 to 3), collecting input for each element.

The Output ProductPrint () Function.

When the control exits the loop, the ProductPrint() function is called, passing the Products variable as a parameter.

Public Function ProdPrint(List As Variant)
Dim j As Integer, k As Integer

'Ubound() function will get the
'total rows in the array - first value in the Dim statement
For j = 0 To UBound(List, 1) - 1
      List(j, 3) = List(j, 1) * List(j, 2)
    For k = 0 To UBound(List, 2) - 1 'get second value in Dim statement
        Debug.Print List(j, k),
    Next k: Debug.Print
Next j

End Function

The ProductPrint() function receives the Products Array reference (address) through the ByRef method.  If the ByVal or ByRef keyword is not explicitly specified before the parameter variable, then ByRef is the default. 

As in the earlier program, two integer variables, j and k, are declared as control variables for the outer and inner For…Next loops. These loops are required to navigate the array using its row and column index numbers. The loop starts at 0, and to determine the end value dynamically, we use the Ubound() (Upper Bound) function on the array dimension. In the previous example, we used values 0 to 3, but here we use Ubound() to make the program flexible. This ensures that if the array size changes later through ReDim statements, the loop still calculates the correct number of rows and columns.

Usage: UBound() Function to get Two-Dimensional Index Numbers.

UBound(Array,1).

The Ubound(List, 1) function returns the number of rows in the array, which is 4. However, array row indexes start from 0 in memory, so the valid index numbers are 0 to 3. The second argument (1) in the Ubound() function specifies that we want the upper bound of the first dimension (rows) of the array. Because the row index starts at 0, we subtract 1 from the total number of rows (4 − 1) when using these indexes in a For…Next loop.

UBound(Array,2).

The UBound(List, 2) function returns the number of columns in the array. The second parameter is optional—if omitted, UBound will return only the upper bound of the first dimension (rows). For single-dimensional arrays, this second parameter is never used.

The statement immediately following the first For…Next loop — List(j, 3) = List(j, 1) * List(j, 2) — calculates the total price of each item and stores it in the rightmost cell. These updated values are then printed in the Debug window during the next 'For… Next' loop, which outputs the complete sales record for each item.

Controlling the Print-Head

Placing a comma at the end of a 'Debug.Print' statement aligns the next item in the 14th column on the same line, following the previously printed item.

An empty 'Debug.Print' statement, placed immediately after the inner Next statement (without a trailing comma), moves the print cursor back to the first column of the next line. This ensures the output for the next sales record begins at the correct position.

If a semicolon (;) is placed at the end of a 'Debug.Print' statement, the print cursor advances to the very next character position, without leaving any space between the printed items.

Next week, we will explore user-defined variables that can hold mixed data types. With them, we can assign meaningful names to each collection element instead of array position, as we did in the examples above. This will make it much easier—and more intuitive—than trying to memorize each array element based solely on its index.


Share:

Function Parameter Array Passing

Function Parameter Array Passing.

    Last week, we explored using ByVal (By Value) and ByRef (By Reference) in function parameters. These keywords determine how data is passed from the calling function to the called function—either as the value stored in a variable (ByVal) or as the memory address (reference) of the variable (ByRef). If you have not yet read that article, you can find the link below:

  • Function Parameter ByVal and ByRef usage.

Now, we will learn:

  1. How to pass the location address of a single element of an Array to the called Function and change its value?

  2. How to pass the Location Address of an array and sort the values in the array in Descending Order.

Two Test Programs.

Function ArrayArg_Test1().

First, let us write two small programs for the first example. The first Program Code is given below.

Public Function ArrayArg_Test1()
Dim NumArray(1 To 5) As Integer, j As Integer

'load array with numbers 1 to 5
For j = 1 To 5
   NumArray(j) = j
Next

'pass a single element value
Call ArrayArg_Test2(NumArray(4))

'Print the Array values after change
For j = 1 To 5
  Debug.Print j, NumArray(j)
Next

End Function

'Result:
1
2
3
20
5

In the first line of code, we define an array variable (NumArray()) with five elements to store integer values. We also declare a control variable j for use in the For…Next loop.

Inside the 'For… Next' loop, the array is populated with values from 1 to 5 — for example, NumArray(1) = 1, NumArray(2) = 2, and so on, up to the fifth element.

Next, we call the ArrayArg_Test2() function, passing NumArray(4) as a parameter. The number 4 inside the brackets refers to the index position of the element being passed—not its value. In this case, the element at index 4 holds the value 4. The ArrayArg_Test2() function receives this argument either as the value itself or as a reference to its memory location, depending on how the function’s parameter is defined.

If the parameter is defined with ByVal, the value is copied into a local variable in the target function, leaving the original array element unchanged. If the parameter is defined with ByRef, the function works directly on the original element, without creating a copy. If neither ByVal nor ByRef is explicitly specified, ByRef is assumed. In our example, we will use this default behavior and omit the ByRef keyword in the function’s parameter definition.

In the next For … Next loop, the Array mcontents are printed in the Debug window. If the ArrayArg_Test2() function has made any changes to the Array, those changes will be reflected in this printed list. Since we already know that the array elements 1 to 5 initially contain the values 1, 2, 3, 4, and 5, we did not print them before calling the second function.

Function ArrayArg_Test2().

The ArrayArg_Test2() Function VBA Code is given below:

Public Function ArrayArg_Test2(NA As Integer) 'The word ByRef is omited
'multiply NumArray(4) value * 5 = 20
    NA = NA * 5
End Function

The Variable NA is assigned to the NumArray’s 4th element location address.  The ArrayArg_Test2() picks the value from NumArray(4) itself, multiplies it by 5, and stores the result back in the same location.

This was working with a single element of an Array.  What about passing the full Array’s location address and working with hundreds of elements of this array in the called Function? 

Get the Array Sorted and Print.

We will now pass the same array used in the previous example to a sorting function, which will arrange its values in descending order.

The modified version of the First Function Code is given below. 

Public Function ArrayArg_Test3()
Dim NumArray(1 To 5) As Integer, j As Integer

For j = 1 To 5
   NumArray(j) = j
Next

'Pass the array to the called function
Call ArrayArg_Test4(NumArray())

'Print the Sorted Array
For j = 1 To 5
  Debug.Print j, NumArray(j)
Next

End Function

Check the function call statement. NumArray() is passed without the number of elements, as we did in the earlier example. The parentheses are required along with the array name to indicate that the parameter is an array, not a single variable.

When control is returned from the ArrayArg_Test4() function, the sorted list of numbers is printed in the debug window. The value printed on the left side is the array element number, and on the right the value itself, sorted in descending order.

Sort the Array in Descending (Z-A) Order.

The Data Sorting Program is given below:

Public Function ArrayArg_Test4(apple() As Integer)
Dim j As Integer, k As Integer, Temp As Integer

'Bubble Sort the Array in Descending order
' 1st loop runs maximum array elements minus 1 times

For j = 1 To 5 - 1 ' in place of 5-1 you may use Ubound(apple)-1

   ' inner loop starts with outer loop's current value + 1
   ' and runs to the maximum number of array elements times

      For k = j + 1 To 5 ' replace 5 with Ubound(apple)

     If apple(k) > apple(j) Then 'if second value is greater
     
        Temp = apple(j) 'copy 1st value to Temp Variable
        
        apple(j) = apple(k) 'move greater value up
        
        apple(k) = Temp ' move the smaller value down
        
     End If
    Next k ' compare next two elements
Next j
End Function

To sort the values in ascending order, the only change needed in the program is to replace the greater-than (>) comparison operator with the less-than (<) operator. In our example, the numbers were already loaded into the array in ascending order.

To make the program work with arrays of any size, replace the fixed constant 5 - 1 in the first For… Next loop with UBound(apple) - 1, and replace 5 in the second loop with UBound(apple). This way, the program can handle arrays without requiring further changes.

Also, note that we omitted the ByRef keyword in the parameter definition of the called ArrayArg_Test4() function. Since ByRef is the default in VBA, the parameter is automatically treated as a ByRef Variable.

Share:

Function Parameter ByVal and ByRef Usage

Function Parameter ByVal and ByRef Usage.

Before exploring this topic, let us review some fundamental concepts of variables for the benefit of beginners.

When we define a variable in VBA or any other programming language, the computer allocates memory cells (let us skip the technical aspect and stick to the basics) to store a value there. 

In a layman's analogy, we can imagine a variable as a box with the name 'Apple' or whatever name we give to the box, and use that name to pick the value stored in it.  Assume that you have put 5 Apples in the box named A.  We can give these apples to someone in two ways.

  1. Make a copy of the apples (the number) from box A, and put the copy into another box B.  The owner of box B can work with his apples, adding more Apples to box B or removing a few from B, and so on.  There will not be any change to the contents of box A. 
  2. We can tell the other person (or Function) which area of the room (location) you kept the original Apples box A, and it can take control of box A and work with it directly. No need for a new Box B. But if you would like to preserve the original Box contents for some other purpose, then make a copy into some other box and work.

Making Copies of Original Values.

In the first method explained above, unlike the physical box, you can make copies of the original value and store them in different Variables.  The Function that receives the value has no access to the first Variable.  The Function can work with the copy of the value received. 

In the second case, you can tell the location of the Apple_Box1 to the other Function so that it can go there and find its contents and do whatever the Function wants to do with them (add, subtract, multiply, etc., or use it as part of other calculations) or whatever operations you would like to do with them. 

To prove the first point above, let us take a closer look at the next two example functions, Test_1A() (the calling function) and Test_1B() (the called function with the copy of the value).

Method-1 Examples:

Public Function Test_1A()

'Define two variables(boxes)to hold values of
'Long Integer type
Dim Apple_Box1 As Long, ApplesReceived As Long

'Put an initial value of 10

'into the variable

Apple_Box1 = 10

'sending a copy of Apple_Box1 Value to Apple_Box2

'Whatever value changes happened in Apple_Box2 in Test_1B()
'Received back into the third box:ApplesReceived

ApplesReceived = Test_1B(Apple_Box1)

'call funcction Test_1B() with the value
'Display the result in MsgBox.
MsgBox "Apple_Box1: " & Apple_Box1 & vbLf & "Apple_Box2 contents: " & ApplesReceived

End Function

In the code above, we define two variables, Apple_Box1 and ApplesReceived, both of which can hold long integer values. Here, we need not bother about what a long or short integer is, and what range of values can be stored in them, etc.

Next line: Apple_Box1 = 10; the value 10 is stored in Apple_Box1. 

The next three lines are remarks explaining what we do in the next line. 

We are calling the Test_1B() Function and passing the value of Apple_Box1 to the function to do something with the value received in a Variable (ByVal Apple_Box2).  The ByVal before Apple_Box2 given in Test_1B() function states that receive a copy from Apple_Box1 value.  The ‘As Long’ appearing after the closing parenthesis indicates that the second function does some calculations with the received value and returns the result to the first function.  The new value received is stored in the ApplesReceived Variable. 

The Next line, the MsgBox() function, displays the Apple_Box1 contents and the result value (after modifications done to the copy) returned from the Test_1B() function.

The Test_1B Function.

Public Function Test_1B(ByVal Apple_Box2 As Long) As Long

'Take Note of the Key-Word 'ByVal' (says take a Copy of the Passed Value)
'Return the value back into the first function Test_1A
'After adding 15 to the original value of 10
'copied into Apple_Box2

Test_1B = Apple_Box2 + 15

End Function

Only one executable statement in the above function.  Immediately after the function definition, four remark lines indicate what happens in the function.

We will take a closer look at the next statement. This statement has two parts – the first part appears left of the equal (=) sign and the second part on the right side, 

In this expression, the left side of the equal sign will be a Variable or a Function name.  By now you may be asking yourself why a function name is there. That is explained in a moment.

The expression on the right side of the equal sign evaluates first, and the result is returned in the left-side variable.  Any existing value in the variable will be lost. You can write that expression in two lines to arrive at the same result as below:


Apple_Box2 = Apple_Box2 + 15

Test_1B = Apple_Box2

In the first expression, you can see that the Apple_Box2 variable is used on both the left and right sides of the = sign. As mentioned earlier, the expression on the right side of the equal sign is evaluated first. Therefore, the existing value of 10 stored in Apple_Box2 is used in the calculation, and 15 is added to it, producing a result of 25. This resulting value is then assigned to the Apple_Box2 variable, replacing its previous value of 10.

If the Function name (the function name given in the first line of Code) appears on the left side of the equal sign, then the meaning of the statement is that the final result of the expression must be returned to the Calling Function.  Here, the Function Name acts as a Variable with the Data Type (As Long) specified immediately after the closing brackets on the first line.

This function name appears on the calling statement in the calling function to the right side of the = sign, and a Variable Name on the left side of the = sign that saves the received result value (ApplesReceived = Test_1B(Apple_Box1).

Method-2 Examples:

In this method, we defined only one variable, Apple_Box1, as a Long Integer Type.  In the next line, the Variable is loaded with an initial value of 10.  The next two lines are remarks explaining the next line of code, which calls the second function Test_1D().

Compare this statement with the statement that calls Test_1B.  Unlike the statement that calls Test_1B(), the Function Test_1D and the function parameter, Variable Apple_Box1, only appear here.  The opening and closing brackets are omitted from the function name. The parameter variable is the second item.  Test_1D() function returns no value to the calling function Test_1C.  Therefore, we don't need to write this line of code in the form of an expression as we did in the Test_1A Function.  But you cannot write the statement as:

Test_1D(Apple_Box1).,

Once we use the parentheses with the function name around the parameter variable, then VBA assumes that some value is returned from the called function, and you are forced to write it like we did in Function Test_1A:

x = Test_1D(Apple_Box1) 'Expression
'OR use Call statement 
Call Test_1D(Apple_Box1)

There will not be any value in the variable x because no value is returned from the called function.

If you are comfortable with this approach, then you may stick with that. You need to define one more variable for this purpose, and your program needs more memory.

The Call statement needs parentheses around the parameter variable. If there are no parameters to pass to the called function, then it requires the opening and closing parentheses at the end of the function name: Call Test_1D().

When control returns from Test_1D(), the next line displays the changed value in the Apple_Box1 Variable.

Public Function Test_1C()
Dim Apple_Box1 As Long

'put 10 into Variable
Apple_Box1 = 10

'here Test_1D function takes the
'location address of Apple_Box1

Test_1D Apple_Box1 'compare this statement with Test_1A function

MsgBox "Apple_Box1: " & Apple_Box1 & vbLf & "Apple_Box2 contents: " & ApplesReceived

End Function

Test_1D() function takes the parameter's location address. It works directly with the Apple_Box1 value.

Public Function Test_1D(ByRef Apple_Box2 As Long)

Apple_Box2 = Apple_Box2 + 15

End Function

 Test_1D() takes the location address of variable Apple_Box1 into variable Apple_Box2, defined in the parameter area of the Test_1D() Function.

Now, look at the expression:

Apple_Box2 = Apple_Box2 + 15

Apple_Box2 contains the location address, not the contents of the Apple_Box1 variable. But, no need to make any change in the expression to do any kind of calculations.  The expression uses the location address, picks the value from there, and uses it in calculations.

Although we have explicitly used ByRef to specify that the parameter is passed by reference, allowing the function to access the variable's memory address, the rest of the parameter declaration follows the same syntax as any other variable declaration, including the matching data type specification (As Long). Compare the Test_1D() function definition with Test_1B(). The Test_1D() function does not include As Long at the end of its declaration to specify a return data type because it does not return a value. Instead, it directly modifies the original variable's value at its memory location.

You may omit the keyword ByRef from the Function declaration. By default, VBA assumes that the Function Parameter declaration is ByRef (By Reference) if you have not explicitly defined the parameter as ByVal, like:

Public Function Test_1D(Apple_Box2 As Long)

Each Variable Type, like Byte, Integer, Long (integer), Double, String, etc., gets allocated enough memory cells, besides its own address, to hold its maximum range of values. This is different between Programming Languages: VBA, C, C++, C#, etc.

In Conclusion.

We don't have to go too deep, but it does help to know the basics.

If you get in touch with the C language and its variants, like the examples given above, you need to deal with these things, sooner or later.

Our discussion here was on a single variable and its value. How we can work with an Array of Variables and Values. We will explore its methods in the coming weeks.


Share:

PRESENTATION: ACCESS USER GROUPS (EUROPE)

Translate

PageRank

Post Feed


Search

Popular Posts

Blog Archive

Powered by Blogger.

Labels

Forms Functions How Tos MS-Access Security Reports msaccess forms Animations msaccess animation Utilities msaccess controls Access and Internet MS-Access Scurity MS-Access and Internet External Links Queries Array Class Module msaccess reports Accesstips msaccess tips WithEvents Downloads Objects Menus and Toolbars MsaccessLinks Process Controls Art Work Collection Object Property msaccess How Tos Combo Boxes ListView Control Query VBA msaccessQuery Calculation Dictionary Object Event Graph Charts ImageList Control List Boxes TreeView Control Command Buttons Controls Data Emails and Alerts Form Custom Functions Custom Wizards DOS Commands Data Type Key Object Reference ms-access functions msaccess functions msaccess graphs msaccess reporttricks Command Button Report msaccess menus msaccessprocess security advanced Access Security Add Auto-Number Field Type Form Instances ImageList Item Macros Menus Nodes Recordset Top Values Variables msaccess email progressmeter Access2007 Copy Excel Expression Fields Join Methods Microsoft Numbering System RaiseEvent Records Security Split SubForm Table Tables Time Difference Utility WScript Workgroup Wrapper Classes database function msaccess wizards tutorial Access Emails and Alerts Access Fields Access How Tos Access Mail Merge Access2003 Accounting Year Action Animation Attachment Binary Numbers Bookmarks Budgeting ChDir Color Palette Common Controls Conditional Formatting Data Filtering Database Records Defining Pages Desktop Shortcuts Diagram Disk Dynamic Lookup Error Handler Export External Filter Formatting Groups Hexadecimal Numbers Import Labels List Logo Macro Mail Merge Main Form Memo Message Box Monitoring Octal Numbers Operating System Paste Primary-Key Product Rank Reading Remove Rich Text Sequence SetFocus Summary Tab-Page Union Query User Users Water-Mark Word automatically commands hyperlinks iSeries Date iif ms-access msaccess msaccess alerts pdf files reference restore switch text toolbar updating upload vba code