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.
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.
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
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.
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