VBA Procedures

A procedure is a named section of code that is run as a unit. Procedures come in the form of Subs, Functions, Property procedures, Events procedures, and DLL procedures. To call a procedure is to invoke it, executing its code, before control is yielded back to its caller. Procedures can sometimes be called in similar ways but each type of procedure has its own unique capabilities and way it is usually called. Procedures can be called from other procedures and can even be called from themselves recursively.

Summary of Procedure Types

Subs, Functions, Property procedures, Event procedures, and DLL procedures all have their own unique capabilities.

Subs

  • Do not return a value
  • Can be called from the Macro Dialog
  • Can be assigned to shapes and controls

Functions

  • Always return a value
  • Can be used as spreadsheet functions

Property Procedures

  • Used to assign or retrieve the values of properties
  • Are usually used in class modules to control public access to private members

Event Procedures

  • Are associated with objects
  • Are only run when an event is triggered
  • Need to be declared in very specific ways unique to each type of event

DLL Procedures

  • Have their definition in an external DLL file
  • Allow interoperability with other programming languages

Structure of a Procedure

Subs, Functions, Property Procedures, and Event Procedures all follow a very similar structure. Procedures have a signature, a body, and an end statement. DLL procedures are defined externally and are only declared in VBA, so they do not follow the same structure.

Public Function ExampleFunction(Arg1, Arg2, Arg3) As Variant

    'Code statements

    Exit Function

    'Code Statements

End Function

Signature

A procedure's signature declares it's scope, name, parameters, and for functions, return type.

Scope

Procedures can be declared Public, Private, or Friend. Public procedures are accessible from anywhere. Private procedures are only available inside the module in which they are declared. Friend procedures are only available inside the VBA project where they are declared.

Procedures can optionally be declared Static which does not change the procedure's scope, but makes the procedure's local variables hold their values between procedure calls.

Name

A procedure's name must be letters, numbers, and underscores starting with a letter and not exceeding 255 characters. A procedure's name should not be the same as any identifier in VBA to avoid naming collisions.

Parameters

Parameters can be declared inside parentheses. Parameters are required by default but can be declared Optional. Optional parameters can be given a default value. A single ParamArray parameter can be declared after all other parameters which allows a list of values to be passed to a procedure. ParamArray and Optional parameters cannot both be used together in a procedure.

Return Type

A return type can be specified for Functions. See the section on data types for more details on types.

Body

The body of a procedure contains code statements. The body can contain up to 64 kilobytes of code when compiled. If the procedure size exceeds the 64K limit a Procedure Too Large Error will occur. Under most reasonable circumstances a procedure should not get this large so the size limit should not become an issue.

End

The definition of a procedure always ends with an End statement. To exit a procedure early, an Exit statement can be used.

Subs

A Sub (Subroutine) is a procedure that does not return a value. Subs are generally the main entry points to VBA programs. Subs are often called macros.

Declaring Subs

To declare a Sub, provide a Modifier, the Sub keyword, a name for the Sub, any Parameters inside parentheses, and End Sub.

Sub Statement Syntax:

[Public|Private|Friend] [Static] Sub SubName ([Parameter, ...])
    
    [Statements ...]

    [Exit Sub]

    [Statements ...]
    
End Sub

Parameter Syntax:

[Optional] [ByRef|ByVal] [ParamArray] ParameterName [()] [As Type] [ = DefaultValue]

Macro Dialog

The Macro Dialog can be used to run public subroutines that do not have any parameters. The Macro Dialog can be accessed by pressing Alt + F8, through the Developer tab by clicking the Macros icon, and through the View tab by selecting View Macros.

Macro Dialog

Shapes and Controls

To assign a sub to a shape or control insert the shape or control and then right click the shape or control and select assign macro.

Insert Button
Right-click Assign Macro
Assign Macro

Functions

A Function is a procedure that returns a value. Functions can be called from other procedures and the results can be assigned to variables. Public Functions can also be used as spreadsheet functions in Excel.

Declaring Functions

To declare a Function, provide a Modifier, the Function keyword, a name for the Function, any Parameters inside parentheses, and End Function.

Function Statement Syntax:

[Public|Private|Friend] [Static] Function FunctionName ([Parameter, ...])
    
    [Statements ...]

    [Exit Function]

    [Statements ...]
    
End Function

Parameter Syntax:

[Optional] [ByRef|ByVal] [ParamArray] ParameterName [()] [As Type] [ = DefaultValue]

Return Values

A Function will always return a value. To return a specific value from a Function, assign the Function a value before exiting the Function as if the name of the Function were a variable. Functions will return the default value for their data type if no return value is explicitly given. There are some differences in how to return value types, object, and arrays from a function.

Value Types

To return a value type from a function simply assign the value to the function before exiting the function.

Public Function Example() As Boolean

    Example = True

End Function

Object Types

To return an object type from a function use the Set keyword. Also use the Set keyword when assigning the result of the Function to a variable.

Public Sub Example()

    Dim Coll As Collection
    Set Coll = GetCollection()

End Sub

Public Function GetCollection() As Collection

    Set GetCollection = New Collection

End Function

Arrays

To return an array from a function use parentheses after the return type or return a Variant.

Public Sub Example()

    Dim Arr1() As Long
    Arr1 = GetArray1

    Dim Arr2() As Long
    Arr2 = GetArray2

End Sub

Public Function GetArray1() As Long()

    Dim Arr(0 To 2) As Long
    Arr(0) = 1
    Arr(1) = 2
    Arr(2) = 3

    GetArray1 = Arr

End Function

Public Function GetArray2() As Variant

    Dim Arr(0 To 2) As Long
    Arr(0) = 1
    Arr(1) = 2
    Arr(2) = 3

    GetArray2 = Arr

End Function

Spreadsheet Functions (User-Defined Functions - UDFs)

Public Functions can be used in spreadsheets like built-in Excel functions. UDF arguments will not automatically appear as a tooltip. To show the arguments in the function press Ctrl + Alt + a.

Public Function AddTwoNumbers(Num1 As Double, Num2 As Double) As Double

    AddTwoNumbers = Num1 + Num2

End Function
UDF Arguments

Property Procedures

Property procedures are normally used to assign and retrieve the values of private variables in a class. However, they can also be used in Standard modules. There are three types of property procedures: Get, Let, and Set. Get is used to return a value. Let and Set are used to assign a value. Set must be used for object types and Let is used for all other types. Property procedures will be discussed again in the section on classes.

Declaring Property Procedures

To declare a Property Procedure use the Property keyword with Get, Let, or Set inside a Class Module or a Standard Module and either assign or return a value.

'Class Module: Class1

Option Explicit

Private pMessage As String
Private pColl    As Collection

Public Property Get Message() As String
    Message = pMessage
End Property

Public Property Let Message (RHS As String)
    pMessage = RHS
End Property

Public Property Get Coll() As Collection
    Set Coll = pColl
End Property

Public Property Set Coll(RHS As Collection)
    Set pColl = RHS
End Property

Using Property Procedures

To call a Property Procedure assign or retrieve the value of the property of a class instance or module.

'Standard Module: Module1

Option Explicit

Public Sub Example()

    Dim C1 As Class1
    Set C1 = New Class1
    
    'Assign properties
    C1.Message = "Hello, World!" 'Let procedure is called
    Set C1.Coll = New Collection 'Set procedure is called
    
    'Retrieve properties
    Debug.Print C1.Message     'Get procedure is called
    Debug.Print C1.Coll.Count  'Get procedure is called

End Sub

Event Procedures

Event procedures are subroutines that are run when an event is triggered. Event Procedures can be used with built-in objects or custom events can be created. Some examples of events are opening an Excel Workbook, changing the value of a cell on an Excel Worksheet, loading a UserForm, and interacting with a control. Event procedures follow specific declaration rules and should be added to a module using the object box and procedure box in the code window. Event procedures are described in more detail in the Events section.

Workbook Open Event Procedure

DLL Procedures

DLL Procedures are defined in a DLL file and are declared in VBA using the Declare statement. DLL procedures will be discussed in the sections on the Windows API, Compiler Directives, and Interoperability.

#If VBA7 = 1 Then
    Declare PtrSafe Sub Sleep Lib "kernel32" Alias "Sleep" (ByVal dwMilliseconds As Long)
#Else
    Declare Sub Sleep Lib "kernel32" Alias "Sleep" (ByVal dwMilliseconds As Long)
#End If

Calling Procedures

The way a procedure is called depends on the type of procedure. Subs are just simply called. Arguments must not be surrounded by parentheses. The result of Functions can be assigned to variables or passed as an argument to another procedure. If the result of a Function is stored or passed the function's arguments must be surrounded by parentheses, otherwise parentheses must be omitted. The Call statement can be used to call Subs, Functions, and DLL procedures. The Call statement is not required. If the Call statement is used, arguments must be enclosed in parentheses.

Public Sub Example()

    PrintMessage "Hello, World!"       'Cannot have parentheses
    Call PrintMessage("Hello, World!") 'Must have parentheses

    Dim Result As Long
    AddTwoNumbers 1, 2                 'Function can be called without assigning the result.
    Call AddTwoNumbers(1, 2)           'Function can be called using call statement
    Result = AddTwoNumbers(1, 2)       'Result of function can be stored
    PrintMessage AddTwoNumbers(1, 2)   'Result of function can be passed as an argument

End Sub

Public Sub PrintMessage(Message As String)
    Debug.Print Message
End Sub

Public Function AddTwoNumbers(Num1 As Long, Num2 As Long)
    AddTwoNumbers = Num1 + Num2
End Function

Parameters and Arguments

Parameters are variables that a procedure solicits from its caller. Arguments are the values passed to a procedure's parameters. All types of procedures can take parameters. Arguments can be passed positionally or by name, and by value or by reference. Arguments can also be declared optional and given a default value. A ParamArray argument can also be declared which accepts a list of arguments of indeterminate size.

Position Vs Keyword

Parameters can be passed to a procedure according to the order they are declared or they can be passed according to the name of the parameter. If an optional parameter is skipped then any subsequent argument will need to be passed by name. Arguments are passed by name using the "walrus" operator (":=").

Public Sub TestExample()

    'Arguments are passed according to the position
    Example 1, 2, 3 

    'Arguments are passed according to name
    Example Parameter1:=1, Parameter2:=2, Parameter3:=3 

    'Parameter1 is skipped. Next arguments must be passed by name
    Example Parameter2:="Hello", Parameter3:="World!"

End Sub


Public Sub Example(Optional Parameter1, Optional Parameter2, Optional Parameter3)

    Debug.Print Parameter1, Parameter2, Parameter3

End Sub

ByRef vs ByVal

The ByRef or ByVal keywords can be used in the declaration of a procedure's parameters to specify if the argument is passed by reference or by value. When an argument is passed by reference, the variable itself is passed to the procedure and its value can be changed by the procedure. When an argument is passed by value, a copy of the variable is created locally and passed to the procedure so the original variable cannot be affected. If ByRef or ByVal is not specified, arguments are passed by reference. The behavior of passing arguments ByRef or ByVal differs depending on if the argument is a value type variable, a reference type variable, or an array.

Value Types

When value type variables are passed ByRef the value can be directly changed. When value type variables are passed ByVal a copy is made of the variable. Value type variables can be passed by value even if the parameter is declared ByRef by wrapping the argument in parentheses.

Public Sub TestExample()

    Dim x As Long
    x = 1

    ByValExample x

    Debug.Print x 'Prints: 1

    ByRefExample x

    Debug.Print x 'Prints: 2

    ByRefExample (x)

    Debug.Print x 'Prints: 2

End Sub

Public Sub ByValExample(ByVal y As Long)

    'y is a copy of x
    y = y + 1

End Sub

Public Sub ByRefExample(ByRef y As Long)

    'y is x
    y = y + 1

End Sub

Reference Types

Objects and User-Defined types are both reference types. User-Defined types cannot be passed ByVal. Object variables store a reference to the object's data. When an object variable is passed ByRef, a reference is being passed by reference. This means that the reference can be changed to point to a different object and if no other references to the original object exist then the object will be terminated. When an object variable is passed ByVal, a copy of a reference is being passed. The copy of the reference is still pointing to the same data as the original reference. However, if the object variable is reassigned to another object then the original reference variable is still pointing to the original object and the object cannot be terminated.

Private Sub TestExample()

    Dim Coll As Collection
    Set Coll = New Collection

    Coll.Add 1

    Debug.Print Coll.Count

    ByValExample Coll

    Debug.Print Coll.Count

    ByRefExample Coll

    Debug.Print Coll.Count

End Sub

Private Sub ByValExample(ByVal Coll As Collection)

    'Coll is a copy of the object variable
    'Coll is pointing to the original object

    'Original object is affected
    Coll.Add 2

    'Reference is reassigned to new object
    'Original object is NOT terminated b/c original reference variable still points to it
    Set Coll = New Collection

End Sub

Private Sub ByRefExample(ByRef Coll As Collection)

    'Coll is the original object variable

    'Original object is affected
    Coll.Add 3

    'Reference is reassigned to new object
    'Original object is terminated b/c no more references to it
    Set Coll = New Collection

End Sub

Arrays

Arguments passed to parameters that are declared as arrays must be passed ByRef. However, arrays can be passed ByVal to parameters declared as Variant.

Public Sub Example()

    Dim Arr(0 To 2) As Variant
    Arr(0) = 1
    Arr(1) = 2
    Arr(2) = 3

    ByRefExample Arr

    Debug.Print Arr(0), Arr(1), Arr(2)

    ByValExample Arr

    Debug.Print Arr(0), Arr(1), Arr(2)

End Sub

Public Sub ByRefExample(ByRef Arr() As Variant)

    'Array parameters must be declared ByRef.

    'Original array values are updated
    Arr(0) = 4
    Arr(1) = 5
    Arr(2) = 6

End Sub

Public Sub ByValExample(ByVal Arr As Variant)

    'Copy is made of array

    'Original array values not affected
    Arr(0) = 7
    Arr(1) = 8
    Arr(2) = 9

End Sub

Optional Parameters and Default Values

Optional parameters can be declared using the "Optional" keyword. A default value can be assigned for optional parameters. Optional parameters must be declared after required arguments. Optional parameters cannot be declared if there is a ParamArray parameter.

Public Sub Example(Message As String, Optional Flag As Boolean = True)

    Debug.Print Message, Flag

End Sub

The IsMissing function can be used to determine if an argument was passed to an optional parameter of type Variant. To check if an optional argument of another type has been passed, use a default value which specifies that no valid argument was passed.

Public Sub Example(Optional Arg1 As Variant, Optional Arg2 As String = "NoArgProvided")
    
    If IsMissing(Arg1) Then
        Debug.Print "Arg1 not passed"
    Else
        Debug.Print "Arg1 passed"
    End If

    If Arg2 = "NoArgProvided" Then
        Debug.Print "Arg2 not passed"
    Else
        Debug.Print "Arg2 passed"
    End If                    

End Sub

ParamArray

ParamArray is used to declare a parameter as an array of indeterminate size so any number of arguments can be passed to a procedure. There can only be one ParamArray argument in a procedure declaration and it must be the last parameter. ParamArray cannot be used if the procedure has any Optional arguments. Arguments are passed to a ParamArray parameter as a comma-delimited list. A ParamArray must be declared as an array of type Variant.

Public Sub Example()

    Dim TheSum As Double

    TheSum = SumValues(1, 2, 3, 4, 5)

    Debug.Print TheSum

End Sub

Public Function SumValues(ParamArray Values() As Variant) As Double

    Dim i As Long

    For i = LBound(Values) To UBound(Values)
        SumValues = SumValues + Values(i)
    Next i

End Function

Static Procedures

Declaring a procedure using the Static keyword makes all local variables in the procedure static. Static variables maintain their values between procedure calls.

Public Static Sub IncrementCounter()

    'Because procedure is static all local variables are static
    'i will increment with each call to this procedure

    Dim i As Long

    i = i + 1

    Debug.Print i

End Sub

Recursion

Recursive procedures are procedures that call themselves. Recursive procedures must have at least one base case which causes the procedure to stop calling itself, otherwise it will call itself infinitely until VBA runs out of stack space and an error occurs.

Public Sub Example()

    Dim i As Long

    For i = 1 To 5
        Debug.Print Factorial(i)
    Next i

End Sub

Public Function Factorial(N As Long) As Long

    Select Case N

        Case Is > 1
            Factorial = N * Factorial(N - 1)

        Case 1
            Factorial = 1

        Case Is < 1
            Err.Raise 5

    End Select

End Function