VBA Notes For Professionals
VBA Notes For Professionals
VBA
Notes for Professionals
100+ pages
of professional hints and tricks
Disclaimer
GoalKicker.com This is an unocial free book created for educational purposes and is
not aliated with ocial VBA group(s) or company(s).
Free Programming Books All trademarks and registered trademarks are
the property of their respective owners
Contents
About ................................................................................................................................................................................... 1
Chapter 1: Getting started with VBA ................................................................................................................... 2
Section 1.1: Accessing the Visual Basic Editor in Microsoft Oce ............................................................................. 2
Section 1.2: Debugging .................................................................................................................................................. 3
Section 1.3: First Module and Hello World ................................................................................................................... 4
Chapter 2: Declaring Variables .............................................................................................................................. 6
Section 2.1: Type Hints .................................................................................................................................................. 6
Section 2.2: Variables .................................................................................................................................................... 7
Section 2.3: Constants (Const) ................................................................................................................................... 10
Section 2.4: Declaring Fixed-Length Strings ............................................................................................................. 11
Section 2.5: When to use a Static variable ............................................................................................................... 11
Section 2.6: Implicit And Explicit Declaration ............................................................................................................ 13
Section 2.7: Access Modifiers ..................................................................................................................................... 14
Chapter 3: Scripting.FileSystemObject ............................................................................................................ 16
Section 3.1: Retrieve only the path from a file path ................................................................................................. 16
Section 3.2: Retrieve just the extension from a file name ....................................................................................... 16
Section 3.3: Recursively enumerate folders and files .............................................................................................. 16
Section 3.4: Strip file extension from a file name ..................................................................................................... 17
Section 3.5: Enumerate files in a directory using FileSystemObject ...................................................................... 17
Section 3.6: Creating a FileSystemObject ................................................................................................................. 18
Section 3.7: Reading a text file using a FileSystemObject ...................................................................................... 18
Section 3.8: Creating a text file with FileSystemObject ........................................................................................... 19
Section 3.9: Using FSO.BuildPath to build a Full Path from folder path and file name ....................................... 19
Section 3.10: Writing to an existing file with FileSystemObject ............................................................................... 19
Chapter 4: Procedure Calls ................................................................................................................................... 21
Section 4.1: This is confusing. Why not just always use parentheses? .................................................................. 21
Section 4.2: Implicit Call Syntax ................................................................................................................................. 21
Section 4.3: Optional Arguments ............................................................................................................................... 22
Section 4.4: Explicit Call Syntax .................................................................................................................................. 22
Section 4.5: Return Values .......................................................................................................................................... 23
Chapter 5: Naming Conventions ......................................................................................................................... 24
Section 5.1: Variable Names ....................................................................................................................................... 24
Section 5.2: Procedure Names ................................................................................................................................... 27
Chapter 6: Creating a procedure ....................................................................................................................... 29
Section 6.1: Introduction to procedures ..................................................................................................................... 29
Section 6.2: Function With Examples ......................................................................................................................... 29
Chapter 7: Flow control structures .................................................................................................................... 31
Section 7.1: For loop .................................................................................................................................................... 31
Section 7.2: Select Case .............................................................................................................................................. 32
Section 7.3: For Each loop .......................................................................................................................................... 33
Section 7.4: Do loop .................................................................................................................................................... 34
Section 7.5: While loop ................................................................................................................................................ 34
Chapter 8: Comments .............................................................................................................................................. 35
Section 8.1: Apostrophe Comments ........................................................................................................................... 35
Section 8.2: REM Comments ...................................................................................................................................... 35
Chapter 9: Arrays ....................................................................................................................................................... 36
Section 9.1: Multidimensional Arrays ......................................................................................................................... 36
Section 9.2: Dynamic Arrays (Array Resizing and Dynamic Handling) ................................................................ 41
Section 9.3: Jagged Arrays (Arrays of Arrays) ........................................................................................................ 42
Section 9.4: Declaring an Array in VBA ..................................................................................................................... 45
Section 9.5: Use of Split to create an array from a string ...................................................................................... 45
Section 9.6: Iterating elements of an array .............................................................................................................. 47
Chapter 10: Error Handling .................................................................................................................................... 49
Section 10.1: Avoiding error conditions ...................................................................................................................... 49
Section 10.2: Custom Errors ........................................................................................................................................ 49
Section 10.3: Resume keyword ................................................................................................................................... 50
Section 10.4: On Error statement ............................................................................................................................... 52
Chapter 11: Recursion ................................................................................................................................................ 55
Section 11.1: Factorials ................................................................................................................................................. 55
Section 11.2: Folder Recursion .................................................................................................................................... 55
Chapter 12: Conditional Compilation ................................................................................................................ 57
Section 12.1: Changing code behavior at compile time ........................................................................................... 57
Section 12.2: Using Declare Imports that work on all versions of Oce ............................................................... 58
Chapter 13: Data Types and Limits .................................................................................................................... 60
Section 13.1: Variant ..................................................................................................................................................... 60
Section 13.2: Boolean .................................................................................................................................................. 60
Section 13.3: String ....................................................................................................................................................... 61
Section 13.4: Byte ......................................................................................................................................................... 62
Section 13.5: Currency ................................................................................................................................................. 63
Section 13.6: Decimal ................................................................................................................................................... 63
Section 13.7: Integer ..................................................................................................................................................... 63
Section 13.8: Long ........................................................................................................................................................ 63
Section 13.9: Single ...................................................................................................................................................... 64
Section 13.10: Double ................................................................................................................................................... 64
Section 13.11: Date ........................................................................................................................................................ 64
Section 13.12: LongLong .............................................................................................................................................. 65
Section 13.13: LongPtr .................................................................................................................................................. 65
Chapter 14: String Literals - Escaping, non-printable characters and line-continuations
............................................................................................................................................................................................... 66
Section 14.1: Escaping the " character ....................................................................................................................... 66
Section 14.2: Assigning long string literals ................................................................................................................ 66
Section 14.3: Using VBA string constants .................................................................................................................. 66
Chapter 15: Declaring and assigning strings ................................................................................................ 68
Section 15.1: Assignment to and from a byte array ................................................................................................. 68
Section 15.2: Declare a string constant ..................................................................................................................... 68
Section 15.3: Declare a variable-width string variable ............................................................................................ 68
Section 15.4: Declare and assign a fixed-width string ............................................................................................. 68
Section 15.5: Declare and assign a string array ....................................................................................................... 68
Section 15.6: Assign specific characters within a string using Mid statement ....................................................... 69
Chapter 16: Converting other types to strings ............................................................................................ 70
Section 16.1: Use CStr to convert a numeric type to a string .................................................................................. 70
Section 16.2: Use Format to convert and format a numeric type as a string ....................................................... 70
Section 16.3: Use StrConv to convert a byte-array of single-byte characters to a string ................................... 70
Section 16.4: Implicitly convert a byte array of multi-byte-characters to a string ............................................... 70
Chapter 17: Searching within strings for the presence of substrings .............................................. 71
Section 17.1: Use InStr to determine if a string contains a substring ...................................................................... 71
Section 17.2: Use InStrRev to find the position of the last instance of a substring ............................................... 71
Section 17.3: Use InStr to find the position of the first instance of a substring ..................................................... 71
Chapter 18: Substrings ............................................................................................................................................. 72
Section 18.1: Use Left or Left$ to get the 3 left-most characters in a string ......................................................... 72
Section 18.2: Use Right or Right$ to get the 3 right-most characters in a string ................................................. 72
Section 18.3: Use Mid or Mid$ to get specific characters from within a string ...................................................... 72
Section 18.4: Use Trim to get a copy of the string without any leading or trailing spaces ................................. 72
Chapter 19: Measuring the length of strings ................................................................................................ 73
Section 19.1: Use the Len function to determine the number of characters in a string ....................................... 73
Section 19.2: Use the LenB function to determine the number of bytes in a string ............................................. 73
Section 19.3: Prefer `If Len(myString) = 0 Then` over `If myString = "" Then` ......................................................... 73
Chapter 20: Working with ADO ............................................................................................................................ 74
Section 20.1: Making a connection to a data source ............................................................................................... 74
Section 20.2: Creating parameterized commands .................................................................................................. 74
Section 20.3: Retrieving records with a query .......................................................................................................... 75
Section 20.4: Executing non-scalar functions .......................................................................................................... 77
Chapter 21: Concatenating strings .................................................................................................................... 78
Section 21.1: Concatenate an array of strings using the Join function .................................................................. 78
Section 21.2: Concatenate strings using the & operator ......................................................................................... 78
Chapter 22: Assigning strings with repeated characters ....................................................................... 79
Section 22.1: Use the String function to assign a string with n repeated characters ........................................... 79
Section 22.2: Use the String and Space functions to assign an n-character string ............................................. 79
Chapter 23: Scripting.Dictionary object .......................................................................................................... 80
Section 23.1: Properties and Methods ....................................................................................................................... 80
Chapter 24: VBA Option Keyword ...................................................................................................................... 82
Section 24.1: Option Explicit ........................................................................................................................................ 82
Section 24.2: Option Base {0 | 1} ................................................................................................................................ 83
Section 24.3: Option Compare {Binary | Text | Database} ...................................................................................... 84
Chapter 25: Date Time Manipulation ................................................................................................................ 87
Section 25.1: Calendar ................................................................................................................................................. 87
Section 25.2: Base functions ...................................................................................................................................... 87
Section 25.3: Extraction functions .............................................................................................................................. 89
Section 25.4: Calculation functions ............................................................................................................................ 90
Section 25.5: Conversion and Creation ..................................................................................................................... 92
Chapter 26: Creating a Custom Class ............................................................................................................... 94
Section 26.1: Adding a Property to a Class ............................................................................................................... 94
Section 26.2: Class module scope, instancing and re-use ...................................................................................... 95
Section 26.3: Adding Functionality to a Class .......................................................................................................... 95
Chapter 27: Events ..................................................................................................................................................... 97
Section 27.1: Sources and Handlers ........................................................................................................................... 97
Section 27.2: Passing data back to the event source .............................................................................................. 99
Chapter 28: Attributes ........................................................................................................................................... 101
Section 28.1: VB_PredeclaredId ............................................................................................................................... 101
Section 28.2: VB_[Var]UserMemId ......................................................................................................................... 101
Section 28.3: VB_Exposed ........................................................................................................................................ 102
Section 28.4: VB_Description ................................................................................................................................... 103
Section 28.5: VB_Name ............................................................................................................................................ 103
Section 28.6: VB_GlobalNameSpace ...................................................................................................................... 103
Section 28.7: VB_Createable ................................................................................................................................... 104
Chapter 29: User Forms ......................................................................................................................................... 105
Section 29.1: Best Practices ...................................................................................................................................... 105
Section 29.2: Handling QueryClose ......................................................................................................................... 107
Chapter 30: Object-Oriented VBA .................................................................................................................... 109
Section 30.1: Abstraction ........................................................................................................................................... 109
Section 30.2: Encapsulation ..................................................................................................................................... 109
Section 30.3: Polymorphism ..................................................................................................................................... 113
Chapter 31: Working With Files and Directories Without Using FileSystemObject ................. 116
Section 31.1: Determining If Folders and Files Exist ................................................................................................ 116
Section 31.2: Creating and Deleting File Folders .................................................................................................... 117
Chapter 32: Operators ........................................................................................................................................... 118
Section 32.1: Concatenation Operators ................................................................................................................... 118
Section 32.2: Comparison Operators ...................................................................................................................... 118
Section 32.3: Bitwise \ Logical Operators ............................................................................................................... 120
Section 32.4: Mathematical Operators ................................................................................................................... 122
Chapter 33: Collections .......................................................................................................................................... 123
Section 33.1: Getting the Item Count of a Collection .............................................................................................. 123
Section 33.2: Determining if a Key or Item Exists in a Collection ......................................................................... 123
Section 33.3: Adding Items to a Collection ............................................................................................................. 124
Section 33.4: Removing Items From a Collection .................................................................................................. 125
Section 33.5: Retrieving Items From a Collection .................................................................................................. 126
Section 33.6: Clearing All Items From a Collection ................................................................................................ 127
Chapter 34: Passing Arguments ByRef or ByVal ..................................................................................... 129
Section 34.1: Passing Simple Variables ByRef And ByVal ..................................................................................... 129
Section 34.2: ByRef ................................................................................................................................................... 130
Section 34.3: ByVal .................................................................................................................................................... 131
Chapter 35: CreateObject vs. GetObject ...................................................................................................... 133
Section 35.1: Demonstrating GetObject and CreateObject ................................................................................... 133
Chapter 36: Macro security and signing of VBA-projects/-modules ............................................. 134
Section 36.1: Create a valid digital self-signed certificate SELFCERT.EXE ........................................................... 134
Chapter 37: Data Structures ............................................................................................................................... 144
Section 37.1: Linked List ............................................................................................................................................. 144
Section 37.2: Binary Tree .......................................................................................................................................... 145
Chapter 38: Interfaces ........................................................................................................................................... 146
Section 38.1: Multiple Interfaces in One Class - Flyable and Swimable ............................................................... 146
Section 38.2: Simple Interface - Flyable ................................................................................................................. 147
Chapter 39: Reading 2GB+ files in binary in VBA and File Hashes .................................................. 149
Section 39.1: This have to be in a Class module, examples later referred as "Random" .................................. 149
Section 39.2: Code for Calculating File Hash in a Standard module ................................................................... 152
Section 39.3: Calculating all Files Hash from a root Folder .................................................................................. 154
Chapter 40: Sorting ................................................................................................................................................ 158
Section 40.1: Algorithm Implementation - Quick Sort on a One-Dimensional Array ......................................... 158
Section 40.2: Using the Excel Library to Sort a One-Dimensional Array ............................................................ 158
Chapter 41: Frequently used string manipulation ................................................................................... 161
Section 41.1: String manipulation frequently used examples ................................................................................ 161
Chapter 42: Automation or Using other applications Libraries ....................................................... 163
Section 42.1: VBScript Regular Expressions ............................................................................................................ 163
Section 42.2: Scripting File System Object ............................................................................................................. 164
Section 42.3: Scripting Dictionary object ................................................................................................................ 164
Section 42.4: Internet Explorer Object .................................................................................................................... 165
Chapter 43: VBA Run-Time Errors ................................................................................................................... 168
Section 43.1: Run-time error '6': Overflow ............................................................................................................... 168
Section 43.2: Run-time error '9': Subscript out of range ....................................................................................... 168
Section 43.3: Run-time error '13': Type mismatch .................................................................................................. 169
Section 43.4: Run-time error '91': Object variable or With block variable not set .............................................. 169
Section 43.5: Run-time error '20': Resume without error ...................................................................................... 170
Section 43.6: Run-time error '3': Return without GoSub ........................................................................................ 171
Chapter 44: Copying, returning and passing arrays ............................................................................ 173
Section 44.1: Passing Arrays to Proceedures ......................................................................................................... 173
Section 44.2: Copying Arrays ................................................................................................................................... 173
Section 44.3: Returning Arrays from Functions ..................................................................................................... 175
Chapter 45: Non-Latin Characters .................................................................................................................. 177
Section 45.1: Non-Latin Text in VBA Code .............................................................................................................. 177
Section 45.2: Non-Latin Identifiers and Language Coverage .............................................................................. 178
Chapter 46: API Calls .............................................................................................................................................. 179
Section 46.1: Mac APIs ............................................................................................................................................... 179
Section 46.2: Get total monitors and screen resolution ........................................................................................ 179
Section 46.3: FTP and Regional APIs ....................................................................................................................... 180
Section 46.4: API declaration and usage ................................................................................................................ 183
Section 46.5: Windows API - Dedicated Module (1 of 2) ........................................................................................ 185
Section 46.6: Windows API - Dedicated Module (2 of 2) ....................................................................................... 189
Credits ............................................................................................................................................................................ 194
You may also like ...................................................................................................................................................... 195
About
Please feel free to share this PDF with anyone for free,
latest version of this book can be downloaded from:
http://GoalKicker.com/VBABook
This VBA Notes for Professionals book is compiled from Stack Overflow
Documentation, the content is written by the beautiful people at Stack Overflow.
Text content is released under Creative Commons BY-SA, see credits at the end
of this book whom contributed to the various chapters. Images may be copyright
of their respective owners unless otherwise specified
This is an unofficial free book created for educational purposes and is not
affiliated with official VBA group(s) or company(s) nor Stack Overflow. All
trademarks and registered trademarks are the property of their respective
company owners
By default the Developer tab is disabled. To enable the Developer tab go to File -> Options, select Customize Ribbon
in the list on the left. In the right "Customize the Ribbon" treeview find the Developer tree item and set the check
for the Developer checkbox to checked. Click Ok to close the Options dialog.
The Developer tab is now visible in the Ribbon on which you can click on "Visual Basic" to open the Visual Basic
Editor. Alternatively you can click on "View Code" to directly view the code pane of the currently active element, e.g.
WorkSheet, Chart, Shape.
First thing you need to do during debugging is to stop the code at specific locations and then run it line by line to
see whether that happens what's expected.
Breakpoint ( F9 , Debug - Toggle breakpoint): You can add a breakpoint to any executed line (e.g. not to
declarations), when execution reaches that point it stops, and gives control to user.
You can also add the Stop keyword to a blank line to have the code stop at that location on runtime. This is
useful if, for example, before declaration lines to which you can't add a breakpoint with F9
Step into ( F8 , Debug - Step into): executes only one line of code, if that's a call of a user defined sub /
function, then that's executed line by line.
Step over ( Shift + F8 , Debug - Step over): executes one line of code, doesn't enter user defined subs /
functions.
Step out ( Ctrl + Shift + F8 , Debug - Step out): Exit current sub / function (run code until its end).
Watches window
Running code line by line is only the first step, we need to know more details and one tool for that is the watch
window (View - Watch window), here you can see values of defined expressions. To add a variable to the watch
window, either:
When you add a new expression you can choose whether you just want to see it's value, or also break code
execution when it's true or when its value changes.
Immediate Window
The immediate window allows you to execute arbitrary code or print items by preceeding them with either the
Print keyword or a single question mark "?"
Some examples:
* Getting/Setting values for variables via the Immediate Window can only be done during runtime
Whenever your code doesn't work as expected first thing you should do is to read it again carefully, looking for
mistakes.
If that doesn't help, then start debugging it; for short procedures it can be efficient to just execute it line by line, for
longer ones you probably need to set breakpoints or breaks on watched expressions, the goal here is to find the
line not working as expected.
Once you have the line which gives the incorrect result, but the reason is not yet clear, try to simplify expressions,
or replace variables with constants, that can help understanding whether variables' value are wrong.
Include as small part of your code as possible for understanding of your problem
If the problem is not related to the value of variables, then replace them by constants. (so, instead of
Sheets(a*b*c+d^2).Range(addressOfRange) write Sheets(4).Range("A2"))
Describe which line gives the wrong behaviour, and what it is (error, wrong result...)
To test it, hit the Play-Button in your Toolbar or simply hit the F5 key. Congratulations! You've built your first own
VBA Module.
Type hints significantly decrease code readability and encourage a legacy Hungarian Notation which also hinders
readability:
Dim strFile$
Dim iFile%
Instead, declare variables closer to their usage and name things for what they're used, not after their type:
Type hints can also be used on literals, to enforce a specific type. By default, a numeric literal smaller than 32,768
will be interpreted as an Integer literal, but with a type hint you can control that:
Type hints are usually not needed on literals, because they would be assigned to a variable declared with an explicit
type, or implicitly converted to the appropriate type when passed as parameters. Implicit conversions can be
avoided using one of the explicit type conversion functions:
'Calls procedure DoSomething and passes a literal 42 as a Long using a type hint
DoSomething 42&
The majority of the built-in functions that handle strings come in two versions: A loosely typed version that returns
a Variant, and a strongly typed version (ending with $) that returns a String. Unless you are assigning the return
value to a Variant, you should prefer the version that returns a String - otherwise there is an implicit conversion of
the return value.
Note that these are function aliases, not quite type hints. The Left function corresponds to the hidden B_Var_Left
function, while the Left$ version corresponds to the hidden B_Str_Left function.
In very early versions of VBA the $ sign isn't an allowed character and the function name had to be enclosed in
square brackets. In Word Basic, there were many, many more functions that returned strings that ended in $.
At procedure level, using the Dim keyword in any procedure; a local variable.
At module level, using the Private keyword in any type of module; a private field.
At instance level, using the Friend keyword in any type of class module; a friend field.
At instance level, using the Public keyword in any type of class module; a public field.
Globally, using the Public keyword in a standard module; a global variable.
Variables should always be declared with the smallest possible scope: prefer passing parameters to procedures,
rather than declaring global variables.
Local variables
The [As Type] part of the declaration syntax is optional. When specified, it sets the variable's data type, which
determines how much memory will be allocated to that variable. This declares a String variable:
The VBA syntax also supports declaring multiple variables in a single statement:
Notice that the [As Type] has to be specified for each variable (other than 'Variant' ones). This is a relatively
common trap:
Static variables
Local variables can also be Static. In VBA the Static keyword is used to make a variable "remember" the value it
had, last time a procedure was called:
Here the values collection is declared as a Static local; because it's an object variable, it is initialized to Nothing.
The condition that follows the declaration verifies if the object reference was Set before - if it's the first time the
procedure runs, the collection gets initialized. DoSomethingElse might be adding or removing items, and they'll still
be in the collection next time DoSomething is called.
Alternative
VBA's Static keyword can easily be misunderstood - especially by seasoned programmers that usually
work in other languages. In many languages, static is used to make a class member (field, property,
method, ...) belong to the type rather than to the instance. Code in static context cannot reference code
in instance context. The VBA Static keyword means something wildly different.
The Dim keyword is legal at procedure and module levels; its usage at module level is equivalent to using the
Private keyword:
Option Explicit
Dim privateField1 As Long 'same as Private privateField2 as Long
Private privateField2 As Long 'same as Dim privateField2 as Long
The Private keyword is only legal at module level; this invites reserving Dim for local variables and declaring
module variables with Private, especially with the contrasting Public keyword that would have to be used anyway
to declare a public member. Alternatively use Dim everywhere - what matters is consistency:
"Private fields"
"Dim everywhere"
*In general, one should avoid declaring Public or Global fields anyway.
Fields
A variable declared at module level, in the declarations section at the top of the module body, is a field. A Public field
declared in a standard module is a global variable:
A variable with a global scope can be accessed from anywhere, including other VBA projects that would reference
the project it's declared in.
To make a variable global/public, but only visible from within the project, use the Friend modifier:
This is especially useful in add-ins, where the intent is that other VBA projects reference the add-in project and can
consume the public API.
Friend FriendField As Long 'public within the project, aka for "friend" code
Public PublicField As Long 'public within and beyond the project
Instance Fields
'> Class1
Option Explicit
Public PublicField As Long
'> Module1
Option Explicit
Public Sub DoSomething()
'Class1.PublicField means nothing here
With New Class1
.PublicField = 42
End With
'Class1.PublicField means nothing here
End Sub
Encapsulating fields
Instance data is often kept Private, and dubbed encapsulated. A private field can be exposed using a Property
procedure. To expose a private variable publicly without giving write access to the caller, a class module (or a
standard module) implements a Property Get member:
Option Explicit
Private encapsulated As Long
The class itself can modify the encapsulated value, but the calling code can only access the Public members (and
Friend members, if the caller is in the same project).
You can use Const only at module or procedure level. This means the declaration context for a variable must be a
class, structure, module, procedure, or block, and cannot be a source file, namespace, or interface.
Whilst it can be considered good practice to specify Constant types, it isn't strictly required. Not specifying the type
will still result in the correct type:
End Sub
Note that this is specific to Constants and in contrast to variables where not specifying the type results in a Variant
type.
While it is possible to explicitly declare a constant as a String, it is not possible to declare a constant as a string using
fixed-width string syntax
End Sub
Option Explicit
Sub main()
Dim w As Long
For w = 1 To Worksheets.Count
processDictionary ws:=Worksheets(w)
Next w
End Sub
With ws
End With
End Sub
Snippet 2: Create a worksheet UDF that late binds the VBScript.RegExp object
Option Explicit
With rgx
.Global = True
.MultiLine = True
.Pattern = "[0-9]{1,999}"
If .Test(str) Then
Remember that a UDF is not calculated once in the lifetime of a workbook. Even a non-volatile UDF will recalculate
whenever the values within the range(s) it references are subject to change. Each subsequent recalculation event
only increases the benefits of a statically declared variable.
A Static variable is available for the lifetime of the module, not the procedure or function in which it was
declared and assigned.
Static variables can only be declared locally.
Static variable hold many of the same properties of a private module level variable but with a more restricted
scope.
End Sub
In the above code, if Option Explicit is specified, the code will interrupt because it is missing the required Dim
statements for someVariable and someOtherVariable.
Option Explicit
End Sub
It is considered best practice to use Option Explicit in code modules, to ensure that you declare all variables.
Private for private fields, which can only be accessed within the module they're declared in.
Public for public fields and global variables, which can be accessed by any calling code.
Friend for variables public within the project, but inaccessible to other referencing VBA projects (relevant for
add-ins)
Global can also be used for Public fields in standard modules, but is illegal in class modules and is obsolete
anyway - prefer the Public modifier instead. This modifier isn't legal for procedures either.
ModuleVariable = "This can only be done from within the same Module"
End Sub
GlobalVariable = "This can be done from any Module within this Project"
End Sub
Public parameterless Sub procedures in standard modules are exposed as macros and can be attached to controls
and keyboard shortcuts in the host document.
Specifying Option Private Module at the top of a standard module prevents its members from being exposed as
macros and UDF's to the host application.
Note that the trailing path separator is not included in the returned string.
Prints txt Note that the GetExtensionName() method already handles multiple periods in a file name.
Sub EnumerateFilesAndFolders( _
FolderPath As String, _
Optional MaxDepth As Long = -1, _
Optional CurrentDepth As Long = 0, _
Optional Indentation As Long = 2)
C:\Test
Documents
Personal
Budget.xls
Recipes.doc
Work
Planning.doc
Downloads
FooBar.exe
ReadMe.txt
C:\Test
Documents
Downloads
ReadMe.txt
C:\Test
Documents
Personal
Work
Downloads
FooBar.exe
ReadMe.txt
Prints MyFile.something
Note that the GetBaseName() method already handles multiple periods in a file name.
Late bound:
Sub FsoExample()
Dim fso As Object ' declare variable
Set fso = CreateObject("Scripting.FileSystemObject") ' Set it to be a File System Object
Sub ReadTextFileExample()
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
myFilePath = "C:\mypath\to\myfile.txt"
Set sourceFile = fso.OpenTextFile(myFilePath, ForReading)
myFileText = sourceFile.ReadAll ' myFileText now contains the content of the text file
sourceFile.Close ' close the file
myFilePath = "C:\mypath\to\myfile.txt"
Set targetFile = fso.CreateTextFile(myFilePath, True) ' this will overwrite any existing file
targetFile.Write "This is some new text"
targetFile.Write " And this text will appear right after the first bit of text."
targetFile.WriteLine "This bit of text includes a newline character to ensure each write takes
its own line."
targetFile.Close ' close the file
End Sub
Output:
C:\Temp\Results.txt
C:\Temp\Results.txt
Sub WriteTextFileExample()
Dim oFso
Set oFso = CreateObject("Scripting.FileSystemObject")
myFilePath = "C:\mypath\to\myfile.txt"
' First check if the file exists
If oFso.FileExists(myFilePath) Then
' this will overwrite any existing filecontent with whatever you send the file
' to append data to the end of an existing file, use ForAppending instead
Set oFile = oFso.OpenTextFile(myFilePath, ForWriting)
Else
' create the file instead
Set oFile = oFso.CreateTextFile(myFilePath) ' skipping the optional boolean for overwrite if
exists as we already checked that the file doesn't exist.
End If
oFile.Write "This is some new text"
oFile.Write " And this text will appear right after the first bit of text."
oFile.WriteLine "This bit of text includes a newline character to ensure each write takes its
own line."
oFile.Close ' close the file
End Sub
Because they can introduce bugs, both at run-time by passing a possibly unintended value to the procedure, and at
compile-time by simply being invalid syntax.
Run-time
Redundant parentheses can introduce bugs. Given a procedure that takes an object reference as a parameter...
This will raise an "Object Required" runtime error #424. Other errors are possible in other circumstances: here the
Application.ActiveCell Range object reference is being evaluated and passed by value regardless of the
procedure's signature specifying that target would be passed ByRef. The actual value passed ByVal to DoSomething
in the above snippet, is Application.ActiveCell.Value.
Parentheses force VBA to evaluate the value of the bracketed expression, and pass the result ByVal to the called
procedure. When the type of the evaluated result mismatches the procedure's expected type and cannot be
implicitly converted, a runtime error is raised.
Compile-time
Edge case
DoSomething and DoSomethingElse are procedures being called. If the Call keyword was removed, then
DoSomething would be parsed as a line label rather than a procedure call, which would break the code:
For example, if the function, ProcedureName were to have two required arguments (argument1, argument2), and one
optional argument, optArgument3, it could be called at least four ways:
The structure of the function header being called here would look something like this:
The Optional keyword indicates that this argument can be omitted. As mentioned before - any optional arguments
introduced in the header must appear at the end, after any required arguments.
You can also provide a default value for the argument in the case that a value isn't passed to the function:
In this function, if the argument for c isn't supplied it's value will default to "C". If a value is supplied then this will
override the default value.
The explicit call syntax requires the Call keyword and parentheses around the argument list; parentheses are
result = ProcedureName
result = ProcedureName(argument1, argument2)
Parentheses must be present if there are parameters. If the procedure has no parameters, the parentheses are
redundant.
Names of variables that represent an iteratable set of values - e.g. an array, a Collection, a Dictionary, or a Range
of cells, should be plural.
camelCase
End Sub
PascalCase
For Constants:
End Sub
However PascalCase names make cleaner-looking code and are just as good, given IntelliSense uses different icons
for variables and constants:
Name them after what they're used for, not after their data type or scope.
"Hungarian Notation makes it easier to see what the type of a variable is"
If you write your code such as procedures adhere to the Single Responsibility Principle (as it should), you should
never be looking at a screenful of variable declarations at the top of any procedure; declare variables as close as
possible to their first usage, and their data type will always be in plain sight if you declare them with an explicit type.
The VBE's Ctrl + i shortcut can be used to display a variable's type in a tooltip, too.
What a variable is used for is much more useful information than its data type, especially in a language such as VBA
which happily and implicitly converts a type into another as needed.
iFile = FreeFile
Open strFile For Input As #iFile
Input #iFile, strData
bRetVal = True
CleanExit:
Close #iFile
bReadFile = bRetVal
Exit Function
CleanFail:
bRetVal = False
Resume CleanExit
End Function
Compare to:
CleanExit:
Close #handle
CanReadFile = result
Exit Function
CleanFail:
result = False
Resume CleanExit
End Function
strData is passed ByRef in the top example, but beside the fact that we're lucky enough to see that it's explicitly
passed as such, there's no indication that strData is actually returned by the function.
The bottom example names it outContent; this out prefix is what Hungarian Notation was invented for: to help
clarify what a variable is used for, in this case to clearly identify it as an "out" parameter.
This is useful, because IntelliSense by itself doesn't display ByRef, even when the parameter is explicitly passed by
reference:
Hungarian Notation originally didn't have anything to do with variable types. In fact, Hungarian Notation done right
is actually useful. Consider this small example (ByVal and As Integer removed for brevety):
Compare to:
src and dst are Hungarian Notation prefixes here, and they convey useful information that cannot otherwise already
be inferred from the parameter names or IntelliSense showing us the declared type.
Of course there's a better way to convey it all, using proper abstraction and real words that can be pronounced out
loud and make sense - as a contrived example:
Type Coordinate
RowIndex As Long
ColumnIndex As Long
End Type
PascalCase
End Sub
End Function
ObjectName_EventName
End Sub
End Sub
Event handlers are usually automatically named by the VBE; renaming them without renaming the object and/or
the handled event will break the code - the code will run and compile, but the handler procedure will be orphaned
and will never be executed.
Boolean Members
Compare to:
The Can prefix does serve the same purpose as the b prefix: it identifies the function's return value as a Boolean. But
Can reads better than b:
Compared to:
A Function is a procedure that is given data and returns a value, ideally without global or module-scope side-
effects.
A Property is a procedure that encapsulates module data. A property can have up to 3 accessors: Get to return a
value or object reference, Let to assign a value, and/or Set to assign an object reference.
Properties are usually used in class modules (although they are allowed in standard modules as well), exposing
accessor to data that is otherwise inaccessible to the calling code. A property that only exposes a Get accessor is
"read-only"; a property that would only expose a Let and/or Set accessor is "write-only". Write-only properties are
not considered a good programming practice - if the client code can write a value, it should be able to read it back.
Consider implementing a Sub procedure instead of making a write-only property.
Returning a value
A Function or Property Get procedure can (and should!) return a value to its caller. This is done by assigning the
identifier of the procedure:
Function is declared as a return type, as all functions return a value. The Name and the Return Variable of a
function are the Same.
The Function can be called in various ways inside a function. Since a Function declared with a return type is
basically a variable. it is used similar to a variable.
Functional Calls:
call greet() 'Similar to a Procedural call just allows the Procedure to use the
'variable greet
string_1=greet() 'The Return value of the function is used for variable
'assignment
Further the function can also be used as conditions for if and other conditional statements.
for i = 1 to 10
if check_even(i) then
msgbox i & " is Even"
else
msgbox i & " is Odd"
end if
next i
Further more Functions can have modifiers such as By ref and By val for their arguments.
The code above declares an Integer i. The For loop assigns every value between 1 and 10 to i and then executes
Debug.Print i - i.e. the code prints the numbers 1 through 10 to the immediate window. Note that the loop
variable is incremented by the Next statement, that is after the enclosed code executes as opposed to before it
executes.
By default, the counter will be incremented by 1 each time the loop executes. However, a Step can be specified to
change the amount of the increment as either a literal or the return value of a function. If the starting value, ending
value, or Step value is a floating point number, it will be rounded to the nearest integer value. Step can be either a
positive or negative value.
Dim i As Integer
For i = 1 To 10 Step 2
Debug.Print i 'Prints 1, 3, 5, 7, and 9
Next
In general a For loop would be used in situations where it is known before the loop starts how many times to
execute the enclosed code (otherwise a Do or While loop may be more appropriate). This is because the exit
condition is fixed after the first entry into loop, as this code demonstrates:
A For loop can be exited early with the Exit For statement:
Dim i As Integer
For i = 1 To 10
If i > 5 Then
Exit For
End If
Debug.Print i 'Prints 1, 2, 3, 4, 5 before loop exits early.
Next
Sub TestCase()
Dim MyVar As String
Select Case MyVar 'We Select the Variable MyVar to Work with
Case "Hello" 'Now we simply check the cases we want to check
MsgBox "This Case"
Case "World"
MsgBox "Important"
Case "How"
MsgBox "Stuff"
Case "Are"
MsgBox "I'm running out of ideas"
Case "You?", "Today" 'You can separate several conditions with a comma
MsgBox "Uuuhm..." 'if any is matched it will go into the case
Case Else 'If none of the other cases is hit
MsgBox "All of the other cases failed"
End Select
Dim i As Integer
Select Case i
Case Is > 2 '"Is" can be used instead of the variable in conditions.
MsgBox "i is greater than 2"
'Case 2 < Is '"Is" can only be used at the beginning of the condition.
'Case Else is optional
End Select
End Sub
The logic of the SELECT CASE block can be inverted to support testing of different variables too, in this kind of
scenario we can also use logical operators:
Dim x As Integer
Dim y As Integer
x = 2
y = 5
Dim x As Integer
x = 5
Select Case x
Case 1
MsgBox "x equals 1"
Case 2, 3, 4
MsgBox "x is 2, 3 or 4"
Case 7 To 10
MsgBox "x is between 7 and 10 (inclusive)"
Case Is < 2
MsgBox "x is less than one"
Case Is >= 7
MsgBox "x is greater than or equal to 7"
Case Else
MsgBox "no match found"
End Select
End Sub
Avoid For Each when iterating arrays; a For loop will offer significantly better performance with arrays. Conversely,
a For Each loop will offer better performance when iterating a Collection.
Syntax
For Each [item] In [collection]
[statements]
Next [item]
The Next keyword may optionally be followed by the iterator variable; this can help clarify nested loops, although
there are better ways to clarify nested code, such as extracting the inner loop into its own procedure.
'Equivalent to the above loop, but the condition is only checked AFTER the
'first iteration of the loop, so it will execute even at least once even
'if entry is equal to "Stop" before entering the loop (like in this case)
Do
entry = InputBox("Enter a string, Stop to end")
Debug.Print entry
Loop While entry <> "Stop"
Since all lines starting with a comment are ignored, they can also be used to prevent code from executing (while
you debug or refactor). Placing an apostrophe ' before your code turns it into a comment. (This is called
commenting out the line.)
Sub InlineDocumentation()
'Comments start with an "'"
'They can be place before a line of code, which prevents the line from executing
'Debug.Print "Hello World"
Rem Comments CANNOT appear after a statement, use the apostrophe syntax instead
Rem Unless they are preceded by the instruction separator token
Debug.Print "Hello World": Rem prints a welcome message
Debug.Print "Hello World" 'Prints a welcome message
End Sub
As the name indicates, multi dimensional arrays are arrays that contain more than one dimension, usually two or
three but it can have up to 32 dimensions.
A multi array works like a matrix with various levels, take in example a comparison between one, two, and three
Dimensions.
*1D - Visually*
(0)
(1)
(2)
Two Dimensions would look like a Sudoku Grid or an Excel sheet, when initializing the array you would define how
many rows and columns the array would have.
*2D - Visually*
(0,0) (0,1) (0,2)
(1,0) (1,1) (1,2)
(2,0) (2,1) (2,2)
Three Dimensions would start to look like Rubik's Cube, when initializing the array you would define rows and
columns and layers/depths the array would have.
*3D - Visually*
1st layer 2nd layer 3rd layer
front middle back
(0,0,0) (0,0,1) (0,0,2) ¦ (1,0,0) (1,0,1) (1,0,2) ¦ (2,0,0) (2,0,1) (2,0,2)
(0,1,0) (0,1,1) (0,1,2) ¦ (1,1,0) (1,1,1) (1,1,2) ¦ (2,1,0) (2,1,1) (2,1,2)
(0,2,0) (0,2,1) (0,2,2) ¦ (1,2,0) (1,2,1) (1,2,2) ¦ (2,2,0) (2,2,1) (2,2,2)
Further dimensions could be thought as the multiplication of the 3D, so a 4D(1,3,3,3) would be two side-by-side 3D
arrays.
Two-Dimension Array
Creating
The example below will be a compilation of a list of employees, each employee will have a set of information on the
list (First Name, Surname, Address, Email, Phone ...), the example will essentially be storing on the array
(employee,information) being the (0,0) is the first employee's first name.
Bosses = [{"Jonh","Snow","President";"Ygritte","Wild","Vice-President"}]
'initialise a 2D array directly by filling it with information, the redult wil be a array(1,2) size
2x3 = 6 elements
Resizing
Resizing or ReDim Preserve a Multi-Array like the norm for a One-Dimension array would get an error, instead the
information needs to be transferred into a Temporary array with the same size as the original plus the number of
row/columns to add. In the example below we'll see how to initialize a Temp Array, transfer the information over
from the original array, fill the remaining empty elements, and replace the temp array by the original array.
'transfer
For emp = LBound(Employees, 1) To UBound(Employees, 1)
For info = LBound(Employees, 2) To UBound(Employees, 2)
'to transfer Employees into TempEmp we iterate both arrays and fill TempEmp with the
corresponding element value in Employees
TempEmp(emp, info) = Employees(emp, info)
Next
Next
'fill remaining
'after the transfers the Temp array still has unused elements at the end, being that it was increased
'to fill the remaining elements iterate from the last "row" with values to the last row in the array
'in this case the last row in Temp will be the size of the Employees array rows + 1, as the last row
Next
Next
'erase Employees, attribute Temp array to Employees and erase Temp array
Erase Employees
Employees = TempEmp
Erase TempEmp
To change/alter the values in a certain element can be done by simply calling the coordinate to change and giving it
a new value: Employees(0, 0) = "NewValue"
Alternatively iterate through the coordinates use conditions to match values corresponding to the parameters
needed:
Reading
Accessing the elements in the array can be done with a Nested Loop (iterating every element), Loop and Coordinate
(iterate Rows and accessing columns directly), or accessing directly with both coordinates.
'loop and coordinate, iteration through all rows and in each row accessing all columns directly
For emp = LBound(Employees, 1) To UBound(Employees, 1)
Debug.Print Employees(emp, 0)
Debug.Print Employees(emp, 1)
Debug.Print Employees(emp, 2)
Debug.Print Employees(emp, 3)
Debug.Print Employees(emp, 4)
Debug.Print Employees(emp, 5)
Next
Remember, it's always handy to keep an array map when using Multidimensional arrays, they can easily become
Three-Dimension Array
For the 3D array, we'll use the same premise as the 2D array, with the addition of not only storing the Employee
and Information but as well Building they work in.
The 3D array will have the Employees (can be thought of as Rows), the Information (Columns), and Building that can
be thought of as different sheets on an excel document, they have the same size between them, but every sheets
has a different set of information in its cells/elements. The 3D array will contain n number of 2D arrays.
Creating
A 3D array needs 3 coordinates to be initialized Dim 3Darray(2,5,5) As Variant the first coordinate on the array
will be the number of Building/Sheets (different sets of rows and columns), second coordinate will define Rows and
third Columns. The Dim above will result in a 3D array with 108 elements (3*6*6), effectively having 3 different sets
of 2D arrays.
Resizing
Resizing a 3D array is similar to resizing a 2D, first create a Temporary array with the same size of the original
adding one in the coordinate of the parameter to increase, the first coordinate will increase the number of sets in
the array, the second and third coordinates will increase the number of Rows or Columns in each set.
The example below increases the number of Rows in each set by one, and fills those recently added elements with
new information.
Next
Next
Next
'fill remaining
'to fill the remaining elements we need to iterate from the last "row" with values to the last row in
the array in each set, remember that the first empty element is the original array Ubound() plus 1
For building = LBound(TempEmp, 1) To UBound(TempEmp, 1)
For emp = UBound(ThreeDArray, 2) + 1 To UBound(TempEmp, 2)
For info = LBound(TempEmp, 3) To UBound(TempEmp, 3)
Next
Next
Next
'erase Employees, attribute Temp array to Employees and erase Temp array
Erase ThreeDArray
ThreeDArray = TempEmp
Erase TempEmp
Reading and changing the elements on the 3D array can be done similarly to the way we do the 2D array, just adjust
for the extra level in the loops and coordinates.
Do
' using Do ... While for early exit
For building = 0 To UBound(ThreeDArray, 1)
For emp = 0 To UBound(ThreeDArray, 2)
If ThreeDArray(building, emp, 0) = "Gloria" And ThreeDArray(building, emp, 1) =
"Stephan" Then
'if value found
ThreeDArray(building, emp, 1) = "Married, Last Name Change"
Exit Do
'don't iterate through all the array unless necessary
End If
Next
Next
Loop While False
'loop and coordinate, will iterate through all set of rows and ask for the row plus the value we
choose for the columns
Adding and reducing variables on an array dynamically is a huge advantage for when the information you are
treating does not have a set number of variables.
You can simply resize the Array with the ReDim Statement, this will resize the array but to if you which to retain the
information already stored in the array you'll need the part Preserve.
In the example below we create an array and increase it by one more variable in each iteration while preserving the
values already in the array.
For n = 1 To 100
If IsEmpty(Dynamic_array) Then
'isempty() will check if we need to add the first value to the array or subsequent ones
ReDim Dynamic_array(0)
'ReDim Dynamic_array(0) will resize the array to one variable only
Dynamic_array(0) = n
Else
ReDim Preserve Dynamic_array(0 To UBound(Dynamic_array) + 1)
'in the line above we resize the array from variable 0 to the UBound() = last variable, plus
one effectivelly increeasing the size of the array by one
Dynamic_array(UBound(Dynamic_array)) = n
'attribute a value to the last variable of Dynamic_array
End If
Next
We can utilise the same logic to to decrease the the array. In the example the value "last" will be removed from the
array.
We can as well re-utilise the arrays we create as not to have many on memory, which would make the run time
slower. This is useful for arrays of various sizes. One snippet you could use to re-utilise the array is to ReDim the
array back to (0), attribute one variable to to the array and freely increase the array again.
In the snippet below I construct an array with the values 1 to 40, empty the array, and refill the array with values 40
to 100, all this done dynamically.
For n = 1 To 100
If IsEmpty(Dynamic_array) Then
ReDim Dynamic_array(0)
Dynamic_array(0) = n
Next
Arrays of Arrays(Jagged Arrays) are not the same as Multidimensional Arrays if you think about them visually
Multidimensional Arrays would look like Matrices (Rectangular) with defined number of elements on their
dimensions(inside arrays), while Jagged array would be like a yearly calendar with the inside arrays having different
number of elements, like days in on different months.
Although Jagged Arrays are quite messy and tricky to use due to their nested levels and don't have much type
safety, but they are very flexible, allow you to manipulate different types of data quite easily, and don't need to
contain unused or empty elements.
In the below example we will initialise a jagged array containing two arrays one for Names and another for
Numbers, and then accessing one element of each
Debug.Print OuterArray(0)(1)
Debug.Print OuterArray(1)(1)
'accessing elements inside the jagged by giving the coordenades of the element
We can as well be more dynamic in our approx to construct the arrays, imagine that we have a customer data sheet
in excel and we want to construct an array to output the customer details.
We will Dynamically construct an Header array and a Customers array, the Header will contain the column titles and
the Customers array will contain the information of each customer/row as arrays.
For r = 2 To 6
'iterate through the customers/rows
For c = 1 To 4
'iterate through the values/columns
Main_Array(0) = Headers
Main_Array(1) = Customers
To better understand the way to Dynamically construct a one dimensional array please check Dynamic
Arrays (Array Resizing and Dynamic Handling) on the Arrays documentation.
The Result of the above snippet is an Jagged Array with two arrays one of those arrays with 4 elements, 2 indention
levels, and the other being itself another Jagged Array containing 5 arrays of 4 elements each and 3 indention
levels, see below the structure:
To access the information you'll have to bear in mind the structure of the Jagged Array you create, in the above
example you can see that the Main Array contains an Array of Headers and an Array of Arrays (Customers) hence
with different ways of accessing the elements.
Now we'll read the information of the Main Array and print out each of the Customers information as Info Type:
Info.
For n = 0 To UBound(Main_Array(1))
'n to iterate from fisrt to last array in Main_Array(1)
For j = 0 To UBound(Main_Array(1)(n))
'j will iterate from first to last element in each array of Main_Array(1)
REMEMBER to keep track of the structure of your Jagged Array, in the example above to access the Name of a
customer is by accessing Main_Array -> Customers -> CustomerNumber -> Name which is three levels, to return
"Person4" you'll need the location of Customers in the Main_Array, then the Location of customer four on the
Customers Jagged array and lastly the location of the element you need, in this case Main_Array(1)(3)(0) which is
Main_Array(Customers)(CustomerNumber)(Name).
By default, Arrays in VBA are indexed from ZERO, thus, the number inside the parenthesis doesn't refer to the size
of the array, but rather to the index of the last element
Accessing Elements
Accessing an element of the Array is done by using the name of the Array, followed by the index of the element,
inside parenthesis:
Array Indexing
You can change Arrays indexing by placing this line at the top of a module:
Option Base 1
With this line, all Arrays declared in the module will be indexed from ONE.
Specific Index
You can also declare each Array with its own index by using the To keyword, and the lower and upper bound (=
index):
Dynamic Declaration
When you do not know the size of your Array prior to its declaration, you can use the dynamic declaration, and the
ReDim keyword:
Note that using the ReDim keyword will wipe out any previous content of your Array. To prevent this, you can use
the Preserve keyword after ReDim:
Syntax
Part Description
Required. String expression containing substrings and delimiters. If expression is a zero-length string(""
expression or vbNullString), Split returns an empty array containing no elements and no data. In this case, the
returned array will have a LBound of 0 and a UBound of -1.
Optional. String character used to identify substring limits. If omitted, the space character (" ") is
delimiter assumed to be the delimiter. If delimiter is a zero-length string, a single-element array containing the
entire expression string is returned.
limit Optional. Number of substrings to be returned; -1 indicates that all substrings are returned.
Optional. Numeric value indicating the kind of comparison to use when evaluating substrings. See
compare
Settings section for values.
Settings
Example
In this example it is demonstrated how Split works by showing several styles. The comments will show the result set
for each of the different performed Split options. Finally it is demonstrated how to loop over the returned string
array.
Sub Test
Using the iterator variable as the index number is the fastest way to iterate the elements of an array:
For Each...Next
A For Each...Next loop can also be used to iterate arrays, if performance doesn't matter:
A For Each loop will iterate all dimensions from outer to inner (the same order as the elements are laid out in
memory), so there is no need for nested loops:
Note that For Each loops are best used to iterate Collection objects, if performance matters.
0
1
2
3
One key element in reducing runtime errors, is writing small procedures that do one thing. The fewer reasons
procedures have to fail, the easier the code as a whole is to debug.
This error will be raised when an object is used before its reference is assigned. One might have a procedure that
receives an object parameter:
If target isn't assigned a reference, the above code will raise an error that is easily avoided by checking if the object
contains an actual object reference:
If target isn't assigned a reference, then the unassigned reference is never used, and no error occurs.
This way of early-exiting a procedure when one or more parameter isn't valid, is called a guard clause.
Given an index greater than the number of worksheets in the ActiveWorkbook, the above code will raise a runtime
error. A simple guard clause can avoid that:
Most runtime errors can be avoided by carefully verifying the values we're using before we use them, and branching
on another execution path accordingly using a simple If statement - in guard clauses that makes no assumptions
and validates a procedure's parameters, or even in the body of larger procedures.
Option Explicit
Public Enum FoobarError
Err_FooWasNotBarred = vbObjectError + 1024
Err_BarNotInitialized
Err_SomethingElseHappened
End Enum
Using the vbObjectError built-in constant ensures the custom error codes don't overlap with reserved/existing
error codes. Only the first enum value needs to be explicitly specified, for the underlying value of each Enum
member is 1 greater than the previous member, so the underlying value of Err_BarNotInitialized is implicitly
vbObjectError + 1025.
A runtime error can be raised using the Err.Raise statement, so the custom Err_FooWasNotBarred error can be
raised as follows:
Err.Raise Err_FooWasNotBarred
The Err.Raise method can also take custom Description and Source parameters - for this reason it's a good idea
to also define constants to hold each custom error's description:
The class' implementation can then simply call these specialized procedures to raise the error:
The client code can then handle Err_BarNotInitialized as it would any other error, inside its own error-handling
subroutine.
Note: the legacy Error keyword can also be used in place of Err.Raise, but it's obsolete/deprecated.
run to the end of the procedure, in which case execution resumes in the calling procedure.
or, use the Resume keyword to resume execution inside the same procedure.
There are several ways an error-handling subroutine may use the Resume keyword:
Resume used alone, execution continues on the statement that caused the error. If the error isn't actually
handled before doing that, then the same error will be raised again, and execution might enter an infinite
loop.
Resume Next continues execution on the statement immediately following the statement that caused the
error. If the error isn't actually handled before doing that, then execution is permitted to continue with
potentially invalid data, which may result in logical errors and unexpected behavior.
Resume [line label] continues execution at the specified line label (or line number, if you're using legacy-
style line numbers). This would typically allow executing some cleanup code before cleanly exiting the
procedure, such as ensuring a database connection is closed before returning to the caller.
The On Error statement itself can use the Resume keyword to instruct the VBA runtime to effectively ignore all
errors.
If the error isn't actually handled before doing that, then execution is permitted to continue with potentially
invalid data, which may result in logical errors and unexpected behavior.
The emphasis above cannot be emphasized enough. On Error Resume Next effectively ignores all errors and
shoves them under the carpet. A program that blows up with a runtime error given invalid input is a better
program than one that keeps running with unknown/unintended data - be it only because the bug is much more
easily identifiable. On Error Resume Next can easily hide bugs.
The On Error statement is procedure-scoped - that's why there should normally be only one, single such On Error
statement in a given procedure.
However sometimes an error condition can't quite be avoided, and jumping to an error-handling subroutine only to
Resume Next just doesn't feel right. In this specific case, the known-to-possibly-fail statement can be wrapped
between two On Error statements:
The On Error GoTo 0 instruction resets error handling in the current procedure, such that any further instruction
causing a runtime error would be unhandled within that procedure and instead passed up the call stack until it is
caught by an active error handler. If there is no active error handler in the call stack, it will be treated as an
unhandled exception.
Callee
Exit Sub
Handler:
Debug.Print "Error " & Err.Number & " in Caller."
Exit Sub
Handler:
Debug.Print "Error " & Err.Number & " in Callee."
Resume Next
End Sub
Line labels denote subroutines: because subroutines originate from legacy BASIC code and uses GoTo and GoSub
jumps and Return statements to jump back to the "main" routine, it's fairly easy to write hard-to-follow spaghetti
code if things aren't rigorously structured. For this reason, it's best that:
This means a procedure that handles its errors, should be structured like this:
CleanExit:
'cleanup code here
Exit Sub
CleanFail:
'error-handling code here
Resume CleanExit
End Sub
Sometimes you want to handle different errors with different actions. In that case you will inspect the global Err
object, which will contain information about the error that was raised - and act accordingly:
CleanExit:
Exit Sub
CleanFail:
Select Case Err.Number
Case 9
MsgBox "Specified number doesn't exist. Please try again.", vbExclamation
Resume
Case 91
As a general guideline, consider turning on the error handling for entire subroutine or function, and handle all the
errors that may occur within its scope. If you need to only handle errors in the small section section of the code --
turn error handling on and off a the same level:
If CheckValue = 0 Then
On Error GoTo ErrorHandler ' turn error handling on
' code that may result in error
On Error GoTo 0 ' turn error handling off - same level
End If
CleanExit:
Exit Sub
ErrorHandler:
' error handling code here
' do not turn off error handling here
Resume
End Sub
Line numbers
VBA supports legacy-style (e.g. QBASIC) line numbers. The Erl hidden property can be used to identify the line
number that raised the last error. If you're not using line numbers, Erl will only ever return 0.
Sub DoSomething()
10 On Error GoTo 50
20 Debug.Print 42 / 0
30 Exit Sub
40
50 Debug.Print "Error raised on line " & Erl ' returns 20
End Sub
If you are using line numbers, but not consistently, then Erl will return the last line number before the instruction that
raised the error.
Sub DoSomething()
10 On Error GoTo 50
Debug.Print 42 / 0
30 Exit Sub
Keep in mind that Erl also only has Integer precision, and will silently overflow. This means that line numbers
outside of the integer range will give incorrect results:
Sub DoSomething()
The line number isn't quite as relevant as the statement that caused the error, and numbering lines quickly
becomes tedious and not quite maintenance-friendly.
See Recursion.
Sub EnumerateFilesAndFolders( _
FolderPath As String, _
Optional MaxDepth As Long = -1, _
Optional CurrentDepth As Long = 0, _
Optional Indentation As Long = 2)
#Const DEBUGMODE = 1
This results in the value of filepath being set to "C:\Users\UserName\Path\To\File.txt". Removing the #Const
line, or changing it to #Const DEBUGMODE = 0 would result in the filepath being set to
"\\server\share\path\to\file.txt".
#Const Scope
The #Const directive is only effective for a single code file (module or class). It must be declared for each and every
file you wish to use your custom constant in. Alternatively, you can declare a #Const globally for your project by
going to Tools >> [Your Project Name] Project Properties. This will bring up the project properties dialog box where
we’ll enter the constant declaration. In the “Conditional Compilation Arguments” box, type in [constName] =
[value]. You can enter more than 1 constant by separating them with a colon, like [constName1] = [value1] :
[constName2] = [value2].
Pre-defined Constants
Some compilation constants are already pre-defined. Which ones exist will depend on the bitness of the office
version you're running VBA in. Note that Vba7 was introduced alongside Office 2010 to support 64 bit versions of
Office.
Note that Win64/Win32 refer to the Office version, not the Windows version. For example Win32 = TRUE in 32-bit
Office, even if the OS is a 64-bit version of Windows.
This can be simplified a bit depending on what versions of office you need to support. For example, not many
people are still supporting 16 bit versions of Office. The last version of 16 bit office was version 4.3, released in
1994, so the following declaration is sufficient for nearly all modern cases (including Office 2007).
If you don't have to support anything older than Office 2010, this declaration works just fine.
A Variant is a COM data type that is used for storing and exchanging values of arbitrary types, and any other type in
VBA can be assigned to a Variant. Variables declared without an explicit type specified by As [Type] default to
Variant.
Variants are stored in memory as a VARIANT structure that consists of a byte type descriptor (VARTYPE) followed by
6 reserved bytes then an 8 byte data area. For numeric types (including Date and Boolean), the underlying value is
stored in the Variant itself. For all other types, the data area contains a pointer to the underlying value.
The underlying type of a Variant can be determined with either the VarType() function which returns the numeric
value stored in the type descriptor, or the TypeName() function which returns the string representation:
Because Variants can store values of any type, assignments from literals without type hints will be implicitly cast to
a Variant of the appropriate type according to the table below. Literals with type hints will be cast to a Variant of the
hinted type.
Note: Unless there is a specific reason to use a Variant (i.e. an iterator in a For Each loop or an API requirement),
the type should generally be avoided for routine tasks for the following reasons:
They are not type safe, increasing the possibility of runtime errors. For example, a Variant holding an Integer
value will silently change itself into a Long instead of overflowing.
They introduce processing overhead by requiring at least one additional pointer dereference.
The memory requirement for a Variant is always at least 8 bytes higher than needed to store the underlying
type.
A Boolean is used to store values that can be represented as either True or False. Internally, the data type is stored
as a 16 bit value with 0 representing False and any other value representing True.
It should be noted that when a Boolean is cast to a numeric type, all of the bits are set to 1. This results in an
internal representation of -1 for signed types and the maximum value for an unsigned type (Byte).
The casting function to convert to a Boolean is CBool(). Even though it is represented internally as a 16 bit number,
casting to a Boolean from values outside of that range is safe from overflow, although it sets all 16 bits to 1:
Variable length
Dim Value As String
A variable length String allows appending and truncation and is stored in memory as a COM BSTR. This consists of a
4 byte unsigned integer that stores the length of the String in bytes followed by the string data itself as wide
characters (2 bytes per character) and terminated with 2 null bytes. Thus, the maximum string length that can be
handled by VBA is 2,147,483,647 characters.
The internal pointer to the structure (retrievable by the StrPtr() function) points to the memory location of the
data, not the length prefix. This means that a VBA String can be passed directly API functions that require a pointer
to a character array.
Because the length can change, VBA reallocates memory for a String every time the variable is assigned to, which can
impose performance penalties for procedures that alter them repeatedly.
Fixed length
Dim Value As String * 1024 'Declares a fixed length string of 1024 characters.
Fixed length strings are allocated 2 bytes for each character and are stored in memory as a simple byte array. Once
allocated, the length of the String is immutable. They are not null terminated in memory, so a string that fills the
memory allocated with non-null characters is unsuitable for passing to API functions expecting a null terminated
string.
Fixed length strings carry over a legacy 16 bit index limitation, so can only be up to 65,535 characters in length.
Attempting to assign a value longer than the available memory space will not result in a runtime error - instead the
resulting value will simply be truncated:
A Byte is an unsigned 8 bit data type. It can represent integer numbers between 0 and 255 and attempting to store
a value outside of that range will result in runtime error 6: Overflow. Byte is the only intrinsic unsigned type
available in VBA.
The casting function to convert to a Byte is CByte(). For casts from floating point types, the result is rounded to the
nearest integer value with .5 rounding up.
Strings and byte arrays can be substituted for one another through simple assignment (no conversion functions
necessary).
For example:
Sub ByteToStringAndBack()
End Sub
In order to be able to encode Unicode characters, each character in the string takes up two bytes in the array, with
the least significant byte first. For example:
Sub UnicodeExample()
End Sub
A Currency is a signed 64 bit floating point data type similar to a Double, but scaled by 10,000 to give greater
precision to the 4 digits to the right of the decimal point. A Currency variable can store values from
-922,337,203,685,477.5808 to 922,337,203,685,477.5807, giving it the largest capacity of any intrinsic type in a 32 bit
application. As the name of the data type implies, it is considered best practice to use this data type when
representing monetary calculations as the scaling helps to avoid rounding errors.
The Decimal data-type is only available as a sub-type of Variant, so you must declare any variable that needs to
contain a Decimal as a Variant and then assign a Decimal value using the CDec function. The keyword Decimal is a
reserved word (which suggests that VBA was eventually going to add first-class support for the type), so Decimal
cannot be used as a variable or procedure name.
The Decimal type requires 14 bytes of memory (in addition to the bytes required by the parent Variant) and can
store numbers with up to 28 decimal places. For numbers without any decimal places, the range of allowed values
is -79,228,162,514,264,337,593,543,950,335 to +79,228,162,514,264,337,593,543,950,335 inclusive. For numbers
with the maximum 28 decimal places, the range of allowed values is -7.9228162514264337593543950335 to
+7.9228162514264337593543950335 inclusive.
An Integer is a signed 16 bit data type. It can store integer numbers in the range of -32,768 to 32,767 and
attempting to store a value outside of that range will result in runtime error 6: Overflow.
Integers are stored in memory as little-endian values with negatives represented as a two's complement.
Note that in general, it is better practice to use a Long rather than an Integer unless the smaller type is a member of
a Type or is required (either by an API calling convention or some other reason) to be 2 bytes. In most cases VBA
treats Integers as 32 bit internally, so there is usually no advantage to using the smaller type. Additionally, there is a
performance penalty incurred every time an Integer type is used as it is silently cast as a Long.
The casting function to convert to an Integer is CInt(). For casts from floating point types, the result is rounded to
the nearest integer value with .5 rounding up.
A Long is a signed 32 bit data type. It can store integer numbers in the range of -2,147,483,648 to 2,147,483,647 and
Longs are stored in memory as little-endian values with negatives represented as a two's complement.
Note that since a Long matches the width of a pointer in a 32 bit operating system, Longs are commonly used for
storing and passing pointers to and from API functions.
The casting function to convert to a Long is CLng(). For casts from floating point types, the result is rounded to the
nearest integer value with .5 rounding up.
A Single is a signed 32 bit floating point data type. It is stored internally using a little-endian IEEE 754 memory
layout. As such, there is not a fixed range of values that can be represented by the data type - what is limited is the
precision of value stored. A Single can store a value integer values in the range of -16,777,216 to 16,777,216 without
a loss of precision. The precision of floating point numbers depends on the exponent.
A Single will overflow if assigned a value greater than roughly 2128. It will not overflow with negative exponents,
although the usable precision will be questionable before the upper limit is reached.
As with all floating point numbers, care should be taken when making equality comparisons. Best practice is to
include a delta value appropriate to the required precision.
A Double is a signed 64 bit floating point data type. Like the Single, it is stored internally using a little-endian IEEE
754 memory layout and the same precautions regarding precision should be taken. A Double can store integer
values in the range of -9,007,199,254,740,992 to 9,007,199,254,740,992 without a loss of precision. The precision of
floating point numbers depends on the exponent.
A Double will overflow if assigned a value greater than roughly 21024. It will not overflow with negative exponents,
although the usable precision will be questionable before the upper limit is reached.
A Date type is represented internally as a signed 64 bit floating point data type with the value to the left of the
decimal representing the number of days from the epoch date of December 30th, 1899 (although see the note
below). The value to the right of the decimal represents the time as a fractional day. Thus, an integer Date would
have a time component of 12:00:00AM and x.5 would have a time component of 12:00:00PM.
Valid values for Dates are between January 1st 100 and December 31st 9999. Since a Double has a larger range, it is
possible to overflow a Date by assigning values outside of that range.
The casting function to convert to a Date is CDate(), which accepts any numeric type string date/time
representation. It is important to note that string representations of dates will be converted based on the current
locale setting in use, so direct casts should be avoided if the code is meant to be portable.
A LongLong is a signed 64 bit data type and is only available in 64 bit applications. It is not available in 32 bit
applications running on 64 bit operating systems. It can store integer values in the range of
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 and attempting to store a value outside of that range will
result in runtime error 6: Overflow.
LongLongs are stored in memory as little-endian values with negatives represented as a two's complement.
The LongLong data type was introduced as part of VBA's 64 bit operating system support. In 64 bit applications, this
value can be used to store and pass pointers to 64 bit APIs.
The casting function to convert to a LongLong is CLngLng(). For casts from floating point types, the result is
rounded to the nearest integer value with .5 rounding up.
The LongPtr was introduced into VBA in order to support 64 bit platforms. On a 32 bit system, it is treated as a Long
and on 64 bit systems it is treated as a LongLong.
It's primary use is in providing a portable way to store and pass pointers on both architectures (See Changing code
behavior at compile time.
Although it is treated by the operating system as a memory address when used in API calls, it should be noted that
VBA treats it like signed type (and therefore subject to unsigned to signed overflow). For this reason, any pointer
arithmetic performed using LongPtrs should not use > or < comparisons. This "quirk" also makes it possible that
adding simple offsets pointing to valid addresses in memory can cause overflow errors, so caution should be taken
when working with pointers in VBA.
The casting function to convert to a LongPtr is CLngPtr(). For casts from floating point types, the result is rounded
to the nearest integer value with .5 rounding up (although since it is usually a memory address, using it as an
assignment target for a floating point calculation is dangerous at best).
'Output:
'The man said, "Never use air-quotes"
'The man said, "Never use air-quotes"
Debug.Print "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " & _
"Integer hendrerit maximus arcu, ut elementum odio varius " & _
"nec. Integer ipsum enim, iaculis et egestas ac, condiment" & _
"um ut tellus."
'Output:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer hendrerit maximus arcu, ut
elementum odio varius nec. Integer ipsum enim, iaculis et egestas ac, condimentum ut tellus.
VBA will let you use a limited number of line-continuations (the actual number varies by the length of each line
within the continued-block), so if you have very long strings, you'll need to assign and re-assign with concatenation.
Debug.Print loremIpsum
'Output:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer hendrerit maximus arcu, ut
elementum odio varius nec. Integer ipsum enim, iaculis et egestas ac, condimentum ut tellus.
You can use these constants with concatenation and other string functions to build string-literals with special-
characters.
Using vbNullString is considered better practice than the equivalent value of "" due to differences in how the
code is compiled. Strings are accessed via a pointer to an allocated area of memory, and the VBA compiler is smart
enough to use a null pointer to represent vbNullString. The literal "" is allocated memory as if it were a String
typed Variant, making the use of the constant much more efficient:
example = "Testing."
bytes = example 'Direct assignment.
The Mid function will typically appear on the right-hand-side of an assignment statement or in a condition, but the
Mid Statement typically appears on the left hand side of an assignment statement.
'Output:
'Smyth
Note: If you need to assign to individual bytes in a string instead of individual characters within a string (see the
Remarks below regarding the Multi-Byte Character Set), the MidB statement can be used. In this instance, the
second argument for the MidB statement is the 1-based position of the byte where the replacement will start so the
equivalent line to the example above would be MidB(surname, 5, 2) = "y".
Section 17.3: Use InStr to find the position of the first instance
of a substring
Const baseString As String = "Foo Bar"
Dim containsBar As Boolean
Section 18.4: Use Trim to get a copy of the string without any
leading or trailing spaces
'Trim the leading and trailing spaces in a string
Const paddedText As String = " Foo Bar "
Dim trimmedText As String
trimmedText = Trim$(paddedText)
'trimmedText = "Foo Bar"
charLength = Len(baseString)
'charlength = 11
byteLength = LenB(baseString)
'byteLength = 22
Note that a DSN is not required to connect to a data source via ADO - any data source that has an ODBC provider
can be connected to with the appropriate connection string. While specific connection strings for different
providers are outside of the scope of this topic, ConnectionStrings.com is an excellent reference for finding the
appropriate string for your provider.
With database
.ConnectionString = ConnString
.ConnectionTimeout = 10 'Value is given in seconds.
.Open
End With
OpenDatabaseConnection = database
Exit Function
Handler:
Debug.Print "Database connection failed. Check your connection string."
End Function
Note that the database password is included in the connection string in the example above only for the sake of
clarity. Best practices would dictate not storing database passwords in code. This can be accomplished by taking
the password via user input or using Windows authentication.
In standard ODBC syntax, parameters are given ? "placeholders" in the query text, and then parameters are
appended to the Command in the same order that they appear in the query.
Note: The example above demonstrates a parameterized UPDATE statement, but any SQL statement can be given
parameters.
The first method is to pass the SQL statement directly to the Connection object, and is the easiest method for
executing simple queries:
The second method is to create an ADO Command object for the query you want to execute. This requires a little
more code, but is necessary in order to use parametrized queries:
Note that commands sent to the data source are vulnerable to SQL injection, either intentional or unintentional.
In general, SQL statements should not be created by concatenating user input of any kind. Instead, they should be
parameterized (see Creating parameterized commands).
'Concatenate with Join and separate each element with a 3-character string
concatenatedString = VBA.Strings.Join(widgetNames, " > ")
'concatenatedString = "foo > bar > fizz"
'Concatenate with Join and separate each element with a zero-width string
concatenatedString = VBA.Strings.Join(widgetNames, vbNullString)
'concatenatedString = "foobarfizz"
A dictionary can be thought of as a two field in-memory database with a primary unique index on the first 'field'
(the Key). This unique index on the Keys property allows very fast 'lookups' to retrieve a Key's associated Item value.
Properties
Methods
name description
Adds a new Key and Item to the dictionary. The new key must not exist in the dictionary's current
Add(Key,Item)
Keys collection but an item can be repeated among many unique keys.
Exists(Key) Boolean test to determine if a Key already exists in the dictionary.
Keys Returns the array or collection of unique keys.
Items Returns the array or collection of associated items.
Remove(Key) Removes an individual dictionary key and its associated item.
RemoveAll Clears all of a dictionary object's keys and items.
Sample Code
'Populate, enumerate, locate and remove entries in a dictionary that was created
'with late binding
Sub iterateDictionaryLate()
Dim k As Variant, dict As Object
End Sub
'Populate, enumerate, locate and remove entries in a dictionary that was created
'with early binding (see Remarks)
Sub iterateDictionaryEarly()
Dim d As Long, k As Variant
Dim dict As New Scripting.Dictionary
End Sub
Option Explicit
Sub OptionExplicit()
Dim a As Integer
a = 5
b = 10 '// Causes compile error as 'b' is not declared
End Sub
Setting Require Variable Declaration within the VBE's Tools ► Options ► Editor property page will put the Option
Explicit statement at the top of each newly created code sheet.
By default (and thus if no Option Base is specified), the Base is 0. Which means that the first element of any array
declared in the module has an index of 0.
If Option Base 1 is specified, the first array element has the index 1
Example in Base 0 :
Option Base 0
Sub BaseZero()
For i = 0 To UBound(myStrings)
Debug.Print myStrings(i) ' This will print "Apple", then "Orange", then "Peach"
Next i
End Sub
Sub BaseOne()
For i = 0 To UBound(myStrings)
Next i
End Sub
The second example generated a Subscript out of range (Error 9) at the first loop stage because an attempt to
access the index 0 of the array was made, and this index doesn't exists as the module is declared with Base 1
Debug.Print myStrings(i) ' This will print "Apple", then "Orange", then "Peach"
Next i
It should be noted that the Split function always creates an array with a zero-based element index regardless of
any Option Base setting. Examples on how to use the Split function can be found here
Split Function
Returns a zero-based, one-dimensional array containing a specified number of substrings.
In Excel, the Range.Value and Range.Formula properties for a multi-celled range always returns a 1-based 2D
Variant array.
One recommended 'best practice' is to always use the LBound and UBound functions to determine the extents of
an array.
The Option Base 1 must be at the top of every code module where an array is created or re-dimensioned if arrays
are to be consistently created with an lower boundary of 1.
Binary comparison makes all checks for string equality within a module/class case sensitive. Technically, with this
option, string comparisons are performed using sort order of the binary representations of each character.
A<B<E<Z<a<b<e<z
Sub CompareBinary()
'// "b" (Chr 98) is NOT greater than "á" (Chr 225)
foo = "á"
bar = "b"
End Sub
Option Compare Text makes all string comparisons within a module/class use a case insensitive comparison.
(A | a) < (B | b) < (Z | z)
Sub CompareText()
Option Compare Database is only available within MS Access. It sets the module/class to use the current database
settings to determine whether to use Text or Binary mode.
Note: The use of this setting is discouraged unless the module is used for writing custom Access UDFs (User defined
functions) that should treat text comparisons in the same manner as SQL queries in that database.
Current Calendar : 0
SampleDate = 2016-07-28
Current Calendar : 1
SampleDate = 1437-10-23
VBA supports 3 built-in functions to retrieve the date and/or time from the system's clock.
Sub DateTimeExample()
Timer Function
The Timer function returns a Single representing the number of seconds elapsed since midnight. The precision is
one hundredth of a second.
Sub TimerExample()
End Sub
Because Now and Time functions are only precise to seconds, Timer offers a convenient way to increase accuracy of
time measurement:
Sub GetBenchmark()
Dim i As Long
Dim temp As String
For i = 1 To 1000000 'See how long it takes Left$ to execute 1,000,000 times
temp = Left$("Text", 2)
Next i
End Sub
IsDate()
Sub IsDateExamples()
anything = #9/11/2001#
anything = vbNull
End Sub
Examples:
Sub ExtractionExamples()
End Sub
DatePart() Function
DatePart() is also a function returning a portion of a date, but works differently and allow more possibilities than
the functions above. It can for instance return the Quarter of the year or the Week of the year.
Syntax:
Interval Description
"yyyy" Year (100 to 9999)
"y" Day of the year (1 to 366)
"m" Month (1 to 12)
"q" Quarter (1 to 4)
"ww" Week (1 to 53)
"w" Day of the week (1 to 7)
"d" Day of the month (1 to 31)
"h" Hour (0 to 23)
"n" Minute (0 to 59)
"s" Second (0 to 59)
firstdayofweek is optional. it is a constant that specifies the first day of the week. If not specified, vbSunday is
assumed.
firstweekofyear is optional. it is a constant that specifies the first week of the year. If not specified, the first week is
assumed to be the week in which January 1 occurs.
Examples:
Sub DatePartExample()
End Sub
DateDiff() returns a Long representing the number of time intervals between two specified dates.
Syntax
Examples
End Sub
DateAdd()
DateAdd() returns a Date to which a specified date or time interval has been added.
Syntax
Examples :
Sub DateAddExamples()
End Sub
Sub CDateExamples()
' Find the 10000th day from the epoch date of 1899-12-31
sample = CDate(10000)
Debug.Print Format$(sample, "yyyy-mm-dd") ' prints 1927-05-18
End Sub
Note that VBA also has a loosely typed CVDate() that functions in the same way as the CDate() function other than
returning a date typed Variant instead of a strongly typed Date. The CDate() version should be preferred when
passing to a Date parameter or assigning to a Date variable, and the CVDate() version should be preferred when
when passing to a Variant parameter or assigning to a Variant variable. This avoids implicit type casting.
DateSerial()
DateSerial() function is used to create a date. It returns a Date for a specified year, month, and day.
Syntax:
With year, month and day arguments being valid Integers (Year from 100 to 9999, Month from 1 to 12, Day from 1
to 31).
Examples
Sub DateSerialExamples()
End Sub
Note that DateSerial() will accept "invalid" dates and calculate a valid date from it. This can be used creatively for
good:
Positive Example
Sub GoodDateSerialExample()
End Sub
However, it is more likely to cause grief when attempting to create a date from unvalidated user input:
Negative Example
Sub BadDateSerialExample()
End Sub
Property accessors are often defined in pairs, using both a Get and Let/Set for each property. A property with only
a Get procedure would be read-only, while a property with only a Let/Set procedure would be write-only.
In the following example, four property accessors are defined for the DateRange class:
1. StartDate (read/write). Date value representing the earlier date in a range. Each procedure uses the value of
the module variable, mStartDate.
2. EndDate (read/write). Date value representing the later date in a range. Each procedure uses the value of the
module variable, mEndDate.
3. DaysBetween (read-only). Calculated Integer value representing the number of days between the two dates.
Because there is only a Get procedure, this property cannot be modified directly.
4. RangeToCopy (write-only). A Set procedure used to copy the values of an existing DateRange object.
Private mStartDate As Date ' Module variable to hold the starting date
Private mEndDate As Date ' Module variable to hold the ending date
' Set the starting date value. Note that two methods have the name StartDate
Public Property Let StartDate(ByVal NewValue As Date)
mStartDate = NewValue
End Property
' Read-only property that returns the number of days between the two dates
Public Property Get DaysBetween() As Integer
DaysBetween = DateDiff("d", mStartDate, mEndDate)
End Function
Me.StartDate = ExistingRange.StartDate
Me.EndDate = ExistingRange.EndDate
But often you'll write classes that you'd like to use in other projects without copying the module between projects. If
you define a class called List in ProjectA, and want to use that class in ProjectB, then you'll need to perform 4
actions:
1. Change the instancing property of the List class in ProjectA in the Properties window, from Private to
PublicNotCreatable
2. Create a public "factory" function in ProjectA that creates and returns an instance of a List class. Typically
the factory function would include arguments for the initialization of the class instance. The factory function
is required because the class can be used by ProjectB but ProjectB cannot directly create an instance of
ProjectA's class.
4. In ProjectB, declare a variable and assign it an instance of List using the factory function from ProjectA
Object.Procedure
A Function could return the last day of the next month-end (note that GetFirstDayOfMonth would not be visible
outside the class because it is private):
Procedures can accept arguments of any type, including references to objects of the class being defined.
The following example tests whether the current DateRange object has a starting date and ending date that includes
the starting and ending date of another DateRange object.
Note the use of the Me notation as a way to access the value of the object running the code.
VBA is event-driven: VBA code runs in response to events raised by the host application or the host document -
understanding events is fundamental to understanding VBA.
APIs often expose objects that raise a number of events in response to various states. For example an
Excel.Application object raises an event whenever a new workbook is created, opened, activated, or closed. Or
whenever a worksheet gets calculated. Or just before a file is saved. Or immediately after. A button on a form raises
a Click event when the user clicks it, the user form itself raises an event just after it's activated, and another just
before it's closed.
From an API perspective, events are extension points: the client code can chose to implement code that handles
these events, and execute custom code whenever these events are fired: that's how you can execute your custom
code automatically every time the selection changes on any worksheet - by handling the event that gets fired when
the selection changes on any worksheet.
An object that exposes events is an event source. A method that handles an event is a handler.
Handlers
VBA document modules (e.g. ThisDocument, ThisWorkbook, Sheet1, etc.) and UserForm modules are class modules
that implement special interfaces that expose a number of events. You can browse these interfaces in the left-side
dropdown at the top of the code pane:
The right-side dropdown lists the members of the interface selected in the left-side dropdown:
The VBE automatically generates an event handler stub when an item is selected on the right-side list, or navigates
there if the handler exists.
Each WithEvents declaration becomes available to select from the left-side dropdown. When an event is selected in
the right-side dropdown, the VBE generates an event handler stub named after the WithEvents object and the
name of the event, joined with an underscore:
End Sub
End Sub
Only types that expose at least one event can be used with WithEvents, and WithEvents declarations cannot be
assigned a reference on-the-spot with the New keyword. This code is illegal:
The object reference must be Set explicitly; in a class module, a good place to do that is often in the
Class_Initialize handler, because then the class handles that object's events for as long as its instance exists.
Sources
Any class module (or document module, or user form) can be an event source. Use the Event keyword to define the
signature for the event, in the declarations section of the module:
The signature of the event determines how the event is raised, and what the event handlers will look like.
Events can only be raised within the class they're defined in - client code can only handle them. Events are raised
with the RaiseEvent keyword; the event's arguments are provided at that point:
Without code that handles the SomethingHappened event, running the DoSomething procedure will still raise the
event, but nothing will happen. Assuming the event source is the above code in a class named Something, this code
in ThisWorkbook would show a message box saying "hello" whenever test.DoSomething gets called:
RaiseEvent AfterSomething
End Sub
If the BeforeSomething event has a handler that sets its cancel parameter to True, then when execution returns
from the handler, cancel will be True and AfterSomething will never be raised.
Assuming the foo object reference is assigned somewhere, when foo.DoSomething runs, a message box prompts
whether to cancel, and a second message box says "didn't cancel" only when No was selected.
You could also pass a copy of a mutable object ByVal, and let handlers modify that object's properties; the caller
can then read the modified property values and act accordingly.
Combined with the Variant type, this can be used to create rather non-obvious ways to return a value to the caller:
RaiseEvent SomeEvent(result)
Declaration
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Class1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Call
Debug.Print Class1.GiveMeATwo
In some ways, this simulates the behavior of static classes in other languages, but unlike other languages, you can
still create an instance of the class.
A List class that would encapsulate a Collection would want to have an Item property, so the client code can do
this:
But with a VB_UserMemId attribute set to 0 on the Item property, the client code can do this:
Only one member can legally have VB_UserMemId = 0 in any given class. For properties, specify the attribute in the
Get accessor:
With the magic value -4, the VB_UserMemId attribute tells VBA that this member yields an enumerator - which allows
the client code to do this:
The easiest way to implement this method is by calling the hidden [_NewEnum] property getter on an
internal/encapsulated Collection; the identifier needs to be enclosed in square brackets because of the leading
underscore that makes it an illegal VBA identifier:
Makes the class Private. It cannot be accessed outside of the current project.
Exposes the class Publicly, outside of the project. However, since VB_Createable is ignored in VBA, instances of the
class can not be created directly. This is equivalent to a the following VB.Net class.
In order to get an instance from outside the project, you must expose a factory to create instances. One way of
doing this is with a regular Public module.
Since public modules are accessible from other projects, this allows us to create new instances of our Public - Not
Createable classes.
Note: all accessor members of a property (Get, Let, Set) use the same description.
In VB6, it creates a Default Global Instance of the class (a "shortcut") so that class members can be accessed
without using the class name. For example, DateTime (as in DateTime.Now) is actually part of the VBA.Conversion
Debug.Print VBA.Conversion.DateTime.Now
Debug.Print DateTime.Now
In VB6, it was used in combination with the VB_Exposed attribute to control accessibility of classes outside of the
current project.
VB_Exposed=True
VB_Creatable=True
Would result in a Public Class, that could be accessed from other projects, but this functionality does not exist in
VBA.
Being a class module, a form is therefore a blueprint for an object. Because a form can hold state and data, it's a
better practice to work with a new instance of the class, rather than with the default/global one:
Instead of:
UserForm1.Show vbModal
If Not UserForm1.IsCancelled Then
'...
End If
Working with the default instance can lead to subtle bugs when the form is closed with the red "X" button and/or
when Unload Me is used in the code-behind.
A form should be concerned with nothing but presentation: a button Click handler that connects to a database and
runs a parameterized query based on user input, is doing too many things.
Instead, implement the applicative logic in the code that's responsible for displaying the form, or even better, in
dedicated modules and procedures.
Write the code in such a way that the UserForm is only ever responsible for knowing how to display and collect
data: where the data comes from, or what happens with the data afterwards, is none of its concern.
Make a well-defined model for the form to work with, either in its own dedicated class module, or encapsulated
within the form's code-behind itself - expose the model with Property Get procedures, and have the client code
work with these: this makes the form an abstraction over controls and their nitty-gritty details, exposing only the
relevant data to the client code.
Instead of this:
Forms typically have a Close button, and prompts/dialogs have Ok and Cancel buttons; the user may
close the form using the form's control box (the red "X" button), which destroys the form instance by default
(another good reason to work with a new instance every time).
The simplest way to handle the QueryClose event is to set the Cancel parameter to True, and then to hide the form
instead of closing it:
That way the "X" button will never destroy the instance, and the caller can safely access all the public members.
The code that creates an object should be responsible for destroying it: it's not the form's responsibility to unload
and terminate itself.
Avoid using Unload Me in a form's code-behind. Call Me.Hide instead, so that the calling code can still use the object
it created when the form closes.
Name things.
Use the properties toolwindow ( F4 ) to carefully name each control on a form. The name of a control is used in
the code-behind, so unless you're using a refactoring tool that can handle this, renaming a control will break the
code - so it's much easier to do things right in the first place, than try to puzzle out exactly which of the 20
textboxes TextBox12 stands for.
Whichever style is chosen, anything is better than leaving all controls their default names. Consistency in naming
style is ideal, too.
For better readability, it's best to use these constants instead of using their value directly.
A Cancellable UserForm
Option Explicit
Private Type TView
IsCancelled As Boolean
SomeOtherSetting As Boolean
'other properties skipped for brievety
End Type
Private this As TView
'...more properties...
The calling code could then display the form, and know whether it was cancelled:
The IsCancelled property returns True when the Cancel button is clicked, or when the user closes the form
using the control box.
Abstraction is achieved by implementing functionality with increasingly detailed code. The entry point of a macro
should be a small procedure with a high abstraction level that makes it easy to grasp at a glance what's going on:
The DoSomething procedure has a high abstraction level: we can tell that it's displaying a form and creating some
model, and passing that object to some ProcessUserData procedure that knows what to do with it - how the model
is created is the job of another procedure:
The CreateViewModel function is only responsible for creating some ISomeModel instance. Part of that responsibility
is to acquire an array of available items - how these items are acquired is an implementation detail that's abstracted
behind the GetAvailableItems procedure:
Here the procedure is reading the available values from a named range on a DataSheet worksheet. It could just as
well be reading them from a database, or the values could be hard-coded: it's an implementation detail that's none
of a concern for any of the higher abstraction levels.
The Handling QueryClose example demonstrates encapsulation: the form has a checkbox control, but its client
code doesn't work with it directly - the checkbox is an implementation detail, what the client code needs to know is
whether the setting is enabled or not.
When the checkbox value changes, the handler assigns a private field member:
'...
And when the client code wants to read that value, it doesn't need to worry about a checkbox - instead it simply
uses the SomeOtherSetting property:
The SomeOtherSetting property encapsulates the checkbox' state; client code doesn't need to know that there's a
checkbox involved, only that there's a setting with a Boolean value. By encapsulating the Boolean value, we've added
an abstraction layer around the checkbox.
Let's push that a step further by encapsulating the form's model in a dedicated class module. But if we made a
Public Property for the UserName and Timestamp, we would have to expose Property Let accessors, making the
properties mutable, and we don't want the client code to have the ability to change these values after they're set.
The CreateViewModel function in the Abstraction example returns an ISomeModel class: that's our interface, and it
looks something like this:
Option Explicit
Notice Timestamp and UserName properties only expose a Property Get accessor. Now the SomeModel class can
implement that interface:
The interface members are all Private, and all members of the interface must be implemented for the code to
compile. The Public members are not part of the interface, and are therefore not exposed to code written against
the ISomeModel interface.
Using a VB_PredeclaredId attribute, we can make the SomeModel class have a default instance, and write a function
that works like a type-level (Shared in VB.NET, static in C#) member that the client code can call without needing to
first create an instance, like we did here:
This factory method assigns the property values that are read-only when accessed from the ISomeModel interface,
here Timestamp and UserName:
And now we can code against the ISomeModel interface, which exposes Timestamp and UserName as read-only
properties that can never be reassigned (as long as the code is written against the interface).
The ability to implement interfaces allows completely decoupling the application logic from the UI, or from the
database, or from this or that worksheet.
Say you have an ISomeView interface that the form itself implements:
Option Explicit
Option Explicit
Implements ISomeView
But then, nothing forbids creating another class module that implements the ISomeView interface without being a
user form - this could be a SomeViewMock class:
Option Explicit
Implements ISomeView
And now we can change the code that works with a UserForm and make it work off the ISomeView interface, e.g. by
giving it the form as a parameter instead of instantiating it:
Writing unit tests in VBA can be done, there are add-ins out there that even integrate it into the IDE. But when code
is tightly coupled with a worksheet, a database, a form, or the file system, then the unit test starts requiring an actual
worksheet, database, form, or file system - and these dependencies are new out-of-control failure points that
testable code should isolate, so that unit tests don't require an actual worksheet, database, form, or file system.
By writing code against interfaces, in a way that allows test code to inject stub/mock implementations (like the
above SomeViewMock example), you can write tests in a "controlled environment", and simulate what happens when
every single one of the 42 possible permutations of user interactions on the form's data, without even once
displaying a form and manually clicking on a form control.
To determine if a file exists, simply pass the filename to the Dir$ function and test to see if it returns a result. Note
that Dir$ supports wild-cards, so to test for a specific file, the passed pathName should to be tested to ensure that it
does not contain them. The sample below raises an error - if this isn't the desired behavior, the function can be
changed to simply return False.
The Dir$() function can also be used to determine if a folder exists by specifying passing vbDirectory for the
optional attributes parameter. In this case, the passed pathName value must end with a path separator (\), as
matching filenames will cause false positives. Keep in mind that wild-cards are only allowed after the last path
separator, so the example function below will throw a run-time error 52 - "Bad file name or number" if the input
contains a wild-card. If this isn't the desired behavior, uncomment On Error Resume Next at the top of the
function. Also remember that Dir$ supports relative file paths (i.e. ..\Foo\Bar), so results are only guaranteed to
be valid as long as the current working directory is not changed.
The ChDir statement can also be used to test if a folder exists. Note that this method will temporarily change the
environment that VBA is running in, so if that is a consideration, the Dir$ method should be used instead. It does
have the advantage of being much less forgiving with its parameter. This method also supports relative file paths,
so has the same caveat as the Dir$ method.
The MkDir statement can be used to create a new folder. It accepts paths containing drive letters (C:\Foo), UNC
names (\\Server\Foo), relative paths (..\Foo), or the current working directory (Foo).
If the drive or UNC name is omitted (i.e. \Foo), the folder is created on the current drive. This may or may not be the
same drive as the current working directory.
The RmDir statement can be used to delete existing folders. It accepts paths in the same forms as MkDir and uses
the same relationship to the current working directory and drive. Note that the statement is similar to the Windows
rd shell command, so will throw a run-time error 75: "Path/File access error" if the target directory is not empty.
If the & operator is used with a variable type other than a String, it is implicitly cast to a String before being
concatenated.
Note that the + concatenation operator is an overload of the + addition operator. The behavior of + is determined
by the variable types of the operands and precedence of operator types. If both operands are typed as a String or
Variant with a sub-type of String, they are concatenated:
left = "5"
right = "5"
If either side is a numeric type and the other side is a String that can be coerced into a number, the type
precedence of mathematical operators causes the operator to be treated as the addition operator and the numeric
values are added:
left = 5
right = "5"
This behavior can lead to subtle, hard to debug errors - especially if Variant types are being used, so only the &
operator should typically be used for concatenation.
The VBA syntax allows for "chains" of comparison operators, but these constructs should generally be avoided.
Comparisons are always performed from left to right on only 2 operands at a time, and each comparison results in
a Boolean. For example, the expression...
a = 2: b = 1: c = 0
expr = a > b > c
...may be read in some contexts as a test of whether b is between a and c. In VBA, this evaluates as follows:
a = 2: b = 1: c = 0
expr = a > b > c
expr = (2 > 1) > 0
expr = True > 0
expr = -1 > 0 'CInt(True) = -1
expr = False
Any comparison operator other than Is used with an Object as an operand will be performed on the return value
of the Object's default member. If the object does not have a default member, the comparison will result in a Run-
time error 438 - "Object doesn't support his property or method".
If the Object is unintitialized, the comparison will result in a Run-time error 91 - "Object variable or With block
variable not set".
If the literal Nothing is used with any comparison operator other than Is, it will result in a Compile error - "Invalid
use of object".
If the default member of the Object is another Object, VBA will continually call the default member of each
successive return value until a primitive type is returned or an error is raised. For example, assume SomeClass has a
default member of Value, which is an instance of ChildClass with a default member of ChildValue. The
comparison...
If either operand is a numeric type and the other operand is a String or Variant of subtype String, a numeric
comparison will be performed. In this case, if the String cannot be cast to a number, a Run-time error 13 - "Type
mismatch" will result from the comparison.
If both operands are a String or a Variant of subtype String, a string comparison will be performed based on the
Option Compare setting of the code module. These comparisons are performed on a character by character basis.
left = "42"
right = "5"
Debug.Print left > right 'Prints False
Debug.Print Val(left) > Val(right) 'Prints True
End Sub
For this reason, make sure that String or Variant variables are cast to numbers before performing numeric
inequity comparisons on them.
If one operand is a Date, a numeric comparison on the underlying Double value will be performed if the other
operand is numeric or can be cast to a numeric type.
If the other operand is a String or a Variant of subtype String that can be cast to a Date using the current locale,
the String will be cast to a Date. If it cannot be cast to a Date in the current locale, a Run-time error 13 - "Type
mismatch" will result from the comparison.
Care should be taken when making comparisons between Double or Single values and Booleans. Unlike other
numeric types, non-zero values cannot be assumed to be True due to VBA's behavior of promoting the data type of
a comparison involving a floating point number to Double:
Assigning the result of an expression using one of these operators will give the bitwise result. Note that in the truth
tables below, 0 is equivalent to False and 1 is equivalent to True.
And
Or
Not
Returns True if the expression evaluates to False and False if the expression evaluations to True.
Not is the only operand without a Left-hand operand. The Visual Basic Editor will automatically simplify expressions
with a left hand argument. If you type...
Debug.Print x Not y
Debug.Print Not x
Similar simplifications will be made to any expression that contains a left-hand operand (including expressions) for
Not.
Xor
Also known as "exclusive or". Returns True if both expressions evaluate to different results.
Note that although the Xor operator can be used like a logical operator, there is absolutely no reason to do so as it
gives the same result as the comparison operator <>.
Eqv
Also known as "equivalence". Returns True when both expressions evaluate to the same result.
Imp
Also known as "implication". Returns True if both operands are the same or the second operand is True.
Note that the Imp function is very rarely used. A good rule of thumb is that if you can't explain what it means, you
should use another construct.
Syntax:
.Count()
Sample Usage:
With foo
.Add "One"
.Add "Two"
.Add "Three"
.Add "Four"
End With
Unlike a Scripting.Dictionary, a Collection does not have a method for determining if a given key exists or a way to
retrieve keys that are present in the Collection. The only method to determine if a key is present is to use the
error handler:
If .Number = 0 Then
KeyExistsInCollection = True
ElseIf .Number <> 5 Then
.Raise .Number
End If
End With
End Function
Items
The only way to determine if an item is contained in a Collection is to iterate over the Collection until the item is
located. Note that because a Collection can contain either primitives or objects, some extra handling is needed to
avoid run-time errors during the comparisons:
Syntax:
Notes:
Keys are not case-sensitive. .Add "Bar", "Foo" and .Add "Baz", "foo" will result in a key collision.
If neither of the optional before or after parameters are given, the item will be added after the last item in the
Collection.
Insertions made by specifying a before or after parameter will alter the numeric indexes of existing members
to match thier new position. This means that care should be taken when making insertions in loops using
numeric indexes.
Sample Usage:
Syntax:
.Remove(index)
Parameter Description
The item to remove from the Collection. If the value passed is a numeric type or Variant with a
numeric sub-type, it will be interpreted as a numeric index. If the value passed is a String or Variant
index containing a string, it will be interpreted as the a key. If a String key is passed that does not exist in the
Collection, a Run-time error 5: "Invalid procedure call or argument" will result. If a numeric index is
passed that is does not exist in the Collection, a Run-time error 9: "Subscript out of range" will result.
Notes:
Removing an item from a Collection will change the numeric indexes of all the items after it in the
Collection. For loops that use numeric indexes and remove items should run backwards (Step -1) to
prevent subscript exceptions and skipped items.
Items should generally not be removed from a Collection from inside of a For Each loop as it can give
unpredictable results.
Sample Usage:
With foo
.Add "One"
.Add "Two", "Second"
.Add "Three"
.Add "Four"
End With
Syntax:
.Item(index)
Parameter Description
The item to retrieve from the Collection. If the value passed is a numeric type or Variant with a
numeric sub-type, it will be interpreted as a numeric index. If the value passed is a String or Variant
index containing a string, it will be interpreted as the a key. If a String key is passed that does not exist in the
Collection, a Run-time error 5: "Invalid procedure call or argument" will result. If a numeric index is
passed that is does not exist in the Collection, a Run-time error 9: "Subscript out of range" will result.
Notes:
.Item is the default member of Collection. This allows flexibility in syntax as demonstrated in the sample
usage below.
Numeric indexes are 1-based.
Keys are not case-sensitive. .Item("Foo") and .Item("foo") refer to the same key.
The index parameter is not implicitly cast to a number from a String or visa-versa. It is entirely possible that
.Item(1) and .Item("1") refer to different items of the Collection.
With foo
.Add "One"
.Add "Two"
.Add "Three"
.Add "Four"
End With
With foo
.Add "One", "Foo"
.Add "Two", "Bar"
.Add "Three", "Baz"
End With
Note that bang (!) syntax is allowed because .Item is the default member and can take a single String argument.
The utility of this syntax is questionable.
With foo
.Add "One"
.Add "Two"
.Add "Three"
End With
However, if there are multiple references to the Collection held, this method will only give you an empty
Collection for the variable that is assigned.
With foo
.Add "One"
.Add "Two"
.Add "Three"
End With
Note In many other programming languages (including VB.NET), parameters are implicitly passed by value if no
modifier is specified: consider specifying ByRef modifiers explicitly to avoid possible confusion.
If an argument is passed ByRef, the memory address of the argument is passed to the CalledProcedure and any
modification to that parameter by the CalledProcedure is made to the value in the CallingProcedure.
If an argument is passed ByVal, the actual value, not a reference to the variable, is passed to the CalledProcedure.
Sub CallingProcedure()
Dim A As Long
Dim B As Long
A = 123
B = 456
Debug.Print "BEFORE CALL => A: " & CStr(A), "B: " & CStr(B)
''Result : BEFORE CALL => A: 123 B: 456
Debug.Print "AFTER CALL = A: " & CStr(A), "B: " & CStr(B)
''Result : AFTER CALL => A: 321 B: 456
End Sub
Another example:
Sub Main()
Dim IntVarByVal As Integer
Dim IntVarByRef As Integer
IntVarByVal = 5
IntVarByRef = 10
Watch out! If you're coming to VBA with experience from other languages, this is very likely the exact
opposite behavior to the one you're used to. In many other programming languages (including VB.NET),
the implicit/default modifier passes parameters by value.
Passing by reference
When a value is passed ByRef, the procedure receives a reference to the value.
Calling the above Test procedure outputs 84. DoSomething is given foo and receives a reference to the value,
and therefore works with the same memory address as the caller.
When a reference is passed ByRef, the procedure receives a reference to the pointer.
Using parentheses at the call site, you can override ByRef and force an argument to be passed ByVal:
The above code outputs 42, regardless of whether ByRef is specified implicitly or explicitly.
Watch out! Because of this, using extraneous parentheses in procedure calls can easily introduce bugs.
Pay attention to the whitespace between the procedure name and the argument list:
bar = DoSomething(foo) 'function call, no whitespace; parens are part of args list
DoSomething (foo) 'procedure call, notice whitespace; parens are NOT part of args list
DoSomething foo 'procedure call does not force the foo parameter to be ByVal
When a value is passed ByVal, the procedure receives a copy of the value.
Calling the above Test procedure outputs 42. DoSomething is given foo and receives a copy of the value. The
copy is multiplied by 2, and then discarded when the procedure exits; the caller's copy was never altered.
When a reference is passed ByVal, the procedure receives a copy of the pointer.
Calling the above Test procedure outputs 1. DoSomething is given foo and receives a copy of the pointer to
the Collection object. Because the foo object variable in the Test scope points to the same object, adding
an item in DoSomething adds the item to the same object. Because it's a copy of the pointer, setting its
reference to Nothing does not affect the caller's own copy.
Use the GetObject function when there is a current instance of the object or if you want to create the
object with a file already loaded. If there is no current instance, and you don't want the object started
with a file loaded, use the CreateObject function.
Sub CreateVSGet()
Dim ThisXLApp As Excel.Application 'An example of early binding
Dim AnotherXLApp As Object 'An example of late binding
Dim ThisNewWB As Workbook
Dim AnotherNewWB As Workbook
Dim wb As Workbook
End Sub
Office comes with a utility to create a self-signed digital certificate that you can employ on the PC to sign your
projects.
Click on Digital Certificate for VBA Projects to open the certificate wizard.
In the dialog enter a suitable name for the certificate and click OK.
If you try to employ the certificate you have just created and you check its properties
The certificate has been created in the Current User > Personal > Certificates store. It needs to go in Local
Computer > Trusted Root Certificate Authorities > Certificates store, so you need to export from the former and
import to the latter.
Pressing the Windows Key+R which will open the 'Run' Window. then Enter 'mmc' in the window as shown
below and click 'OK '.
The Microsoft Management Console will open and look like the following.
Expand the dropdown in the left window for Certificates - Current User' and select certificates as shown below. The
center panel will then show the certificates in that location, which will include the certificate you created earlier:
Export Wizard
the Only one pre-selected option will be available, so click 'Next' again:
Expand the Certificates menu and from the Trusted Root Certification Authorities menu, select Certificates.
SinglyLinkedNode class
Option Explicit
LinkedList class
Option Explicit
End Sub
End Function
7
/ \
5 9
/ \ \
3 6 11
\
12
\
14
\
15
BinaryTreeNode class
Option Explicit
BinaryTree class
[TODO]
In VBA, using interfaces lets the compiler check that a module implements all of its methods. A variable or
parameter can be defined in terms of an interface instead of a specific class.
Sub Swim()
' No code
End Sub
Implements Flyable
Implements Swimmable
Implements Swimmable
Now, we can see that the Duck object can be passed to a Sub as a Flyable on one hand, and a Swimmable on the
other:
Sub InterfaceTest()
FlyAndCheckAltitude MyDuck
FlyAndCheckAltitude MyAirplane
TrySwimming MyDuck
TrySwimming MyFish
End Sub
Fly Check...
30
10000
Swim Check...
A class module, Airplane, uses the Implements keyword to tell the compiler to raise an error unless it has two
methods: a Flyable_Fly() sub and a Flyable_GetAltitude() function that returns a Long.
Implements Flyable
Implements Flyable
We can write a routine that accepts any Flyable value, knowing that it will respond to a command of Fly or
GetAltitude:
Because the interface is defined, the IntelliSense popup window will show Fly and GetAltitude for F.
FlyAndCheckAltitude MyDuck
FlyAndCheckAltitude MyAirplane
Note that even though the subroutine is named Flyable_Fly in both Airplane and Duck, it can be called as Fly
when the variable or parameter is defined as Flyable. If the variable is defined specifically as a Duck, it would have
to be called as Flyable_Fly.
Also demonstrate (Read part) for calculating File Hashes without external program like fciv.exe from Microsoft.
Option Explicit
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function FlushFileBuffers Lib "kernel32" (ByVal hFile As Long) As Long
Private uFileSize As Double ' Comment out if not testing performance by FileHashes()
Sub FileHashes()
Dim tStart As Date, tFinish As Date, sHash As String, aTestFiles As Variant, oTestFile As
Variant, aBlockSizes As Variant, oBlockSize As Variant
Dim BLOCKSIZE As Double
' This performs performance testing on different file sizes and block sizes
aBlockSizes = Array("2^12-1", "2^13-1", "2^14-1", "2^15-1", "2^16-1", "2^17-1", "2^18-1",
"2^19-1", "2^20-1", "2^21-1", "2^22-1", "2^23-1", "2^24-1", "2^25-1", "2^26-1")
aTestFiles = Array("C:\ISO\clonezilla-live-2.2.2-37-amd64.iso",
"C:\ISO\HPIP201.2014_0902.29.iso", "C:\ISO\SW_DVD5_Windows_Vista_Business_W32_32BIT_English.ISO",
"C:\ISO\Win10_1607_English_x64.iso",
"C:\ISO\SW_DVD9_Windows_Svr_Std_and_DataCtr_2012_R2_64Bit_English.ISO")
Debug.Print "Test files: " & Join(aTestFiles, " | ")
Debug.Print "BlockSizes: " & Join(aBlockSizes, " | ")
For Each oTestFile In aTestFiles
Debug.Print oTestFile
For Each oBlockSize In aBlockSizes
BLOCKSIZE = Evaluate(oBlockSize)
tStart = Now
sHash = GetFileHash(CStr(oTestFile), BLOCKSIZE, HashTypeMD5)
tFinish = Now
Debug.Print sHash, uFileSize, Format(tFinish - tStart, "hh:mm:ss"), oBlockSize & " (" &
BLOCKSIZE & ")"
Next
Next
End Sub
Private Function GetFileHash(ByVal sFile As String, ByVal uBlockSize As Double, ByVal sHashType As
String) As String
Dim oFSO As Object ' "Scripting.FileSystemObject"
Dim oCSP As Object ' One of the "CryptoServiceProvider"
Dim oRnd As Random ' "Random" Class by Microsoft, must be in the same file
Dim uBytesRead As Double, uBytesToRead As Double, bDone As Boolean
Dim aBlock() As Byte, aBytes As Variant ' Arrays to store bytes
Dim aHash() As Byte, sHash As String, i As Long
'Dim uFileSize As Double ' Un-Comment if GetFileHash() is to be used individually
Application.ScreenUpdating = False
' Process the file in chunks of uBlockSize or less
If uFileSize = 0 Then
ReDim aBlock(0)
oCSP.TransformFinalBlock aBlock, 0, 0
The output is pretty interesting, my test files indicates that BLOCKSIZE = 131071 (2^17-1) gives overall
best performance with 32bit Office 2010 on Windows 7 x64, next best is 2^16-1 (65535). Note 2^27-1
yields Out of memory.
Code
Option Explicit
uFileCount = 0
If oRnd Is Nothing Then Set oRnd = New Random ' Class by Microsoft: Random
If oFSO Is Nothing Then Set oFSO = CreateObject("Scripting.FileSystemObject") ' Just to get
correct FileSize
If oCSP Is Nothing Then Set oCSP = CreateObject("System.Security.Cryptography." & sHashType &
"CryptoServiceProvider")
ProcessFolder oFSO.GetFolder(sRootFDR)
Private Function GetFileHash(ByVal sFile As String, ByVal uBlockSize As Double, ByVal sHashType As
String) As String
Dim uBytesRead As Double, uBytesToRead As Double, bDone As Boolean
Dim aBlock() As Byte, aBytes As Variant ' Arrays to store bytes
Dim aHash() As Byte, sHash As String, i As Long, oTmp As Variant
Dim uFileSize As Double ' Un-Comment if GetFileHash() is to be used individually
If oRnd Is Nothing Then Set oRnd = New Random ' Class by Microsoft: Random
If oFSO Is Nothing Then Set oFSO = CreateObject("Scripting.FileSystemObject") ' Just to get
correct FileSize
If oCSP Is Nothing Then Set oCSP = CreateObject("System.Security.Cryptography." & sHashType &
"CryptoServiceProvider")
There are two types of workarounds: 1) implementing a sorting algorithm from scratch, or 2) using sorting routines
in other commonly-available libraries.
tmpLow = inLow
tmpHi = inHi
Wend
End Sub
Sub testExcelSort()
InitArray arr
ExcelSort arr
End Sub
Const size = 10
ReDim arr(size)
Dim i As Integer
End Sub
With MySort
.SortFields.Clear
.SortFields.Add rng, xlSortOnValues, xlAscending, xlSortNormal
.SetRange rng
.Header = xlNo
.Apply
End With
End Sub
Dim i As Long
Dim c As Range
End Sub
How do you find the text between two search terms (Say: after a colon and before a comma)? How do you get the
remainder of a word (using MID or using RIGHT)? Which of these functions use Zero-based params and return
codes vs One-based? What happens when things go wrong? How do they handle empty strings, unfound results
and negative numbers?
strNull = NOTHING
strEmpty = ""
theText = "1234, 78910"
' -----------------
' Extract the word after the comma ", " and before "910" result: "78" ***
' -----------------
Please feel free to edit this example and make it better. As long as it remains clear, and has in it common usage
practices.
Code
You can use this functions to get RegEx results, concatenate all matches (if more than 1) into 1 string, and display
result in excel cell.
getRegExResult = removeLeadingDelimiter(concatObjectItems(getRegExMatches(RegExObject,
SourceString, RegExPattern, isGlobalSearch, isCaseSensitive), Delimiter), Delimiter)
End Function
With RegExObj
.Global = isGlobalSearch
.IgnoreCase = Not (isCaseSensitive) 'it is more user friendly to use positive meaning of
argument, like isCaseSensitive, than to use negative IgnoreCase
.Pattern = RegExPattern
Set getRegExMatches = .Execute(SourceString)
End With
End Function
The File System Object (FSO) model provides an object-based tool for working with folders and files. It
allows you to use the familiar object.method syntax with a rich set of properties, methods, and events to
process folders and files. You can also employ the traditional Visual Basic statements and commands.
The FSO model gives your application the ability to create, alter, move, and delete folders, or to
determine if and where particular folders exist. It also enables you to get information about folders, such
as their names and the date they were created or last modified.
MSDN-FileSystemObject topics: "...explain the concept of the FileSystemObject and how to use it." exceltrick-
FileSystemObject in VBA – Explained
Scripting.FileSystemObject
Scripting.Dictionary object
MSDN-Dictionary Object
MSDN-InternetExplorer object
The code below should introduce how the IE object works and how to manipulate it through VBA. I recommend
stepping through it, otherwise it might error out during multiple navigations.
Sub IEGetToKnow()
Dim IE As InternetExplorer 'Reference to Microsoft Internet Controls
Set IE = New InternetExplorer
With IE
.Visible = True 'Sets or gets a value that indicates whether the object is visible or
hidden.
'Navigation
.Navigate2 "http://www.example.com" 'Navigates the browser to a location that might not be
expressed as a URL, such as a PIDL for an entity in the Windows Shell namespace.
Debug.Print .Busy 'Gets a value that indicates whether the object is engaged in a navigation
or downloading operation.
Debug.Print .ReadyState 'Gets the ready state of the object.
.Navigate2 "http://www.example.com/2"
.GoBack 'Navigates backward one item in the history list
.GoForward 'Navigates forward one item in the history list.
.GoHome 'Navigates to the current home or start page.
.Stop 'Cancels a pending navigation or download, and stops dynamic page elements, such as
background sounds and animations.
.Refresh 'Reloads the file that is currently displayed in the object.
Debug.Print .Silent 'Sets or gets a value that indicates whether the object can display
dialog boxes.
Debug.Print .Type 'Gets the user type name of the contained document object.
Debug.Print .Top 'Sets or gets the coordinate of the top edge of the object.
Debug.Print .Left 'Sets or gets the coordinate of the left edge of the object.
Debug.Print .Height 'Sets or gets the height of the object.
Debug.Print .Width 'Sets or gets the width of the object.
End With
Web Scraping
The most common thing to do with IE is to scrape some information of a website, or to fill a website form and
submit information. We will look at how to do it.
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is established to be used for illustrative examples in documents. You
may use this
domain in examples without prior coordination or asking for permission.</p>
<p><a href="http://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
Sub IEWebScrape1()
Dim IE As InternetExplorer 'Reference to Microsoft Internet Controls
Set IE = New InternetExplorer
With IE
.Visible = True
.Navigate2 "http://www.example.com"
'We can change the localy displayed website. Don't worry about breaking the site.
.GetElementsByTagName("title")(0).innerHtml = "Psst, scraping..."
.GetElementsByTagName("h1")(0).innerHtml = "Let me try something fishy." 'You have just
changed the local HTML of the site.
.GetElementsByTagName("p")(0).innerHtml = "Lorem ipsum........... The End"
.GetElementsByTagName("a")(0).innerText = "iana.org"
End With '.document
What is going on? The key player here is the .Document, that is the HTML source code. We can apply some queries
to get the Collections or Object we want.
For example the IE.Document.GetElementsByTagName("title")(0).innerHtml. GetElementsByTagName returns a
Collection of HTML Elements, that have the "title" tag. There is only one such tag in the source code. The Collection
is 0-based. So to get the first element we add (0). Now, in our case, we want only the innerHtml (a String), not the
Element Object itself. So we specify the property we want.
Click
Sub IEGoToPlaces()
Dim IE As InternetExplorer 'Reference to Microsoft Internet Controls
Set IE = New InternetExplorer
With IE
.Visible = True
.Navigate2 "http://www.example.com"
Stop 'VBE Stop. Continue line by line to see what happens.
'Click
.Document.GetElementsByTagName("a")(0).Click
Stop 'VBE Stop.
'Return Back
.GoBack
Stop 'VBE Stop.
To get the most out of the HTML that gets loaded into the IE, you can (or should) use another Library, i.e. Microsoft
HTML Object Library. More about this in another example.
IE Main issues
The main issue with IE is verifying that the page is done loading and is ready to be interacted with. The Do While...
Loop helps, but is not reliable.
Also, using IE just to scrape HTML content is OVERKILL. Why? Because the Browser is meant for browsing, i.e.
displaying the web page with all the CSS, JavaScripts, Pictures, Popups, etc. If you only need the raw data, consider
different approach. E.g. using XML HTTPRequest. More about this in another example.
The Integer data type is a 16-bit signed integer with a maximum value of 32,767; assigning it to anything larger
than that will overflow the type and raise this error.
Correct code
Sub DoSomething()
Dim row As Long
For row = 1 To 100000
'do stuff
Next
End Sub
By using a Long (32-bit) integer instead, we can now make a loop that iterates more than 32,767 times without
overflowing the counter variable's type.
Other notes
foo is an array that contains 10 items. When the i loop counter reaches a value of 11, foo(i) is out of range. This
error occurs whenever an array or collection is accessed with an index that doesn't exist in that array or collection.
Correct code
Sub DoSomething()
Dim foo(1 To 10)
Dim i As Long
Use LBound and UBound functions to determine the lower and upper boundaries of an array, respectively.
Other notes
When the index is a string, e.g. ThisWorkbook.Worksheets("I don't exist"), this error means the supplied name
doesn't exist in the queried collection.
The actual error is implementation-specific though; Collection will raise run-time error 5 "Invalid procedure call or
argument" instead:
Sub RaisesRunTimeError5()
Dim foo As New Collection
foo.Add "foo", "foo"
Debug.Print foo("bar")
End Sub
VBA is trying really hard to convert the "42?" argument into a Date value. When it fails, the call to DoSomethingElse
cannot be executed, because VBA doesn't know what date to pass, so it raises run-time error 13 type mismatch,
because the type of the argument doesn't match the expected type (and can't be implicitly converted either).
Correct code
Public Sub DoSomething()
DoSomethingElse Now
End Sub
By passing a Date argument to a procedure that expects a Date parameter, the call can succeed.
Object variables hold a reference, and references need to be set using the Set keyword. This error occurs whenever
a member call is made on an object whose reference is Nothing. In this case foo is a Collection reference, but it's
not initialized, so the reference contains Nothing - and we can't call .Add on Nothing.
Correct code
Sub DoSomething()
Dim foo As Collection
Set foo = New Collection
With foo
.Add "ABC"
.Add "XYZ"
End With
End Sub
By assigning the object variable a valid reference using the Set keyword, the .Add calls succeed.
Other notes
Often, a function or property can return an object reference - a common example is Excel's Range.Find method,
which returns a Range object:
However the function can very well return Nothing (if the search term isn't found), so it's likely that the chained
.Row member call fails.
Before calling object members, verify that the reference is set with a If Not xxxx Is Nothing condition:
CleanFail:
Debug.Print Err.Number
Resume Next
End Sub
Correct Code
Sub DoSomething()
On Error GoTo CleanFail
DoSomethingElse
Exit Sub
CleanFail:
Debug.Print Err.Number
Resume Next
End Sub
By introducing an Exit Sub instruction before the CleanFail line label, we have segregated the CleanFail error-
handling subroutine from the rest of the procedure body - the only way to execute the error-handling subroutine is
via an On Error jump; therefore, no execution path reaches the Resume instruction outside of an error context,
which avoids run-time error 20.
Other notes
This is very similar to Run-time error '3': Return without GoSub; in both situations, the solution is to ensure that the
normal execution path cannot enter a sub-routine (identified by a line label) without an explicit jump (assuming On
Error GoTo is considered an explicit jump).
Execution enters the DoSomething procedure, jumps to the DoThis label, prints "Hi!" to the debug output, returns to
the instruction immediately after the GoSub call, prints "Hi!" again, and then encounters a Return statement, but
there's nowhere to return to now, because we didn't get here with a GoSub statement.
Correct Code
Sub DoSomething()
GoSub DoThis
Exit Sub
DoThis:
Debug.Print "Hi!"
Return
End Sub
By introducing an Exit Sub instruction before the DoThis line label, we have segregated the DoThis subroutine from
Other notes
GoSub/Return is deprecated, and should be avoided in favor of actual procedure calls. A procedure should not
contain subroutines, other than error handlers.
This is very similar to Run-time error '20': Resume without error; in both situations, the solution is to ensure that the
normal execution path cannot enter a sub-routine (identified by a line label) without an explicit jump (assuming On
Error GoTo is considered an explicit jump).
Arrays must be passed by reference. If no passing mechanism is specified, e.g. myFunction(arr()), then VBA will
assume ByRef by default, however it is good coding practice to make it explicit. Trying to pass an array by value, e.g.
myFunction(ByVal arr()) will result in an "Array argument must be ByRef" compilation error (or a "Syntax error"
compilation error if Auto Syntax Check is not checked in the VBE options).
Passing by reference means that any changes to the array will be preserved in the calling proceedure.
Sub testArrayPassing()
Dim source(0 To 1) As Long
source(0) = 3
source(1) = 1
If you want to avoid changing the original array then be careful to write the function so that it doesn't change any
elements.
Alternatively create a working copy of the array and work with the copy.
copyOfArr(0) = copyOfArr(0) * 2
copyOfArr(1) = copyOfArr(1) * 2
The source array can be fixed or dynamic, but the destination array must be dynamic. Trying to copy to a fixed
array will throw a "Can't assign to array" compilation error. Any preexisting data in the receiving array is lost and its
bounds and dimenions are changed to the same as the source array.
Once the copy is made the two arrays are seperate in memory, i.e. the two variables are not references to same
underlying data, so changes made to one array do not appear in the other.
source(0) = 3
source(1) = 1
source(2) = 4
destination = source
destination(0) = 2
With arrays of objects the references to those objects are copied, not the objects themselves. If a change is made to
an object in one array it will also appear to be changed in the other array - they are both referencing the same
object. However, setting an element to a different object in one array won't set it to that object the other array.
destination = source
You can also copy an array into and from a variant variable. When copying from a variant, it must contain an array
of the same type as the receiving array otherwise it will throw a "Type mismatch" runtime error.
var = source
destination = var
var = 5
destination = var ' throws runtime error
outputArray(0) = 3
outputArray(1) = 1
outputArray(2) = 4
arrayOfPiDigits = outputArray
End Function
The result of the function can then be put into a dynamic array of the same type or a variant. The elements can also
be accessed directly by using a second set of brackets, however this will call the function each time, so its best to
store the results in a new array if you plan to use them more than once
Sub arrayExample()
destination = arrayOfPiDigits()
var = arrayOfPiDigits
End Sub
Note that what is returned is actually a copy of the array inside the function, not a reference. So if the function
returns the contents of a Static array its data can't be changed by the calling procedure.
It is normally good coding practice for a procedure's arguments to be inputs and to output via the return value.
Sub printPiDigits()
Dim digits(0 To 2) As Long
threePiDigits digits
Debug.Print digits(0); digits(1); digits(2) ' outputs 3 1 4
End Sub
An output argument can also be used to output an array from a method/proceedure in a Class module
destination(0) = 3
destination(1) = 1
destination(2) = 4
End Sub
mathConsts.threePiDigits digits
Debug.Print digits(0); digits(1); digits(2) ' outputs 3 1 4
End Sub
VBA provides the AscW and ChrW functions to work with multi-byte character codes. We can also use Byte arrays to
manipulate the string variable directly:
Sub NonLatinStrings()
' Printing the entire string to the immediate window fails (all '?'s)
Debug.Print "Whole String" & vbNewLine & rng.Value
Set rng = rng.Offset(1)
Loop
End Sub
This produces the following output for the Arabic Letter Sad:
Whole String
??? ????? ????? ??????? ??????? ??? ??????? — ????? ???????? ???? ??????? ???????
Note that VBA is unable to print non-Latin text to the immediate window even though the string functions work
correctly. This is a limitation of the IDE and not the language.
The extended Latin script has full coverage for many languages:
English, French, Spanish, German, Italian, Breton, Catalan, Danish, Estonian, Finnish, Icelandic, Indonesian, Irish,
Lojban, Mapudungun, Norwegian, Portuguese, Scottish Gaelic, Swedish, Tagalog
Note that in the VBA IDE, a single apostrophe within a variable name does not turn the line into a comment (as it
does on Stack Overflow).
Also, languages that use two angles to indicate a quote «» are allowed to use those in variable names desipte the
fact that the ""-type quotes are not.
API's for VBA imply a set of methods that allow direct interaction with the operating system
Unlike other versions of Office apps that support VBA, Office 2016 for Mac apps are sandboxed.
Sandboxing restricts the apps from accessing resources outside the app container. This affects any add-ins or
macros that involve file access or communication across processes. You can minimize the effects of sandboxing by
using the new commands described in the following section. New VBA commands for Office 2016 for Mac
The following VBA commands are new and unique to Office 2016 for Mac.
Command Use to
GrantAccessToMultipleFiles Request a user's permission to access multiple files at once
AppleScriptTask Call external AppleScript scripts from VB
MAC_OFFICE_VERSION IFDEF between different Mac Office versions at compile time
Private Declare Function system Lib "libc.dylib" (ByVal command As String) As Long
Private Declare Function popen Lib "libc.dylib" (ByVal command As String, ByVal mode As String) As
Long
Private Declare Function pclose Lib "libc.dylib" (ByVal file As Long) As Long
Private Declare Function fread Lib "libc.dylib" (ByVal outStr As String, ByVal size As Long, ByVal
items As Long, ByVal stream As Long) As Long
Private Declare Function feof Lib "libc.dylib" (ByVal file As Long) As Long
Private Declare PtrSafe Function popen Lib "libc.dylib" (ByVal command As String, ByVal mode As
String) As LongPtr
Private Declare PtrSafe Function pclose Lib "libc.dylib" (ByVal file As LongPtr) As Long
Private Declare PtrSafe Function fread Lib "libc.dylib" (ByVal outStr As String, ByVal size As
LongPtr, ByVal items As LongPtr, ByVal stream As LongPtr) As Long
Private Declare PtrSafe Function feof Lib "libc.dylib" (ByVal file As LongPtr) As LongPtr
'VBA Wrappers:
Public Function dllGetMonitors() As Long
Const SM_CMONITORS = 80
dllGetMonitors = GetSystemMetrics32(SM_CMONITORS)
End Function
'Total monitors: 1
'Horizontal Resolution: 1920
'Vertical Resolution: 1080
End Sub
Option Explicit
Option Compare Text
Option Private Module
'http://msdn.microsoft.com/en-us/library/aa384180(v=VS.85).aspx
'http://www.dailydoseofexcel.com/archives/2006/01/29/ftp-via-vba/
'http://www.15seconds.com/issue/981203.htm
'Get a file
Private Declare Function FtpGetFile Lib "wininet.dll" Alias "FtpGetFileA" ( _
ByVal hFtpSession As Long, _
ByVal lpszRemoteFile As String, _
ByVal lpszNewFile As String, _
ByVal fFailIfExists As Boolean, _
ByVal dwFlagsAndAttributes As Long, _
ByVal dwFlags As Long, _
ByVal dwContext As Long _
) As Boolean
'ex: blnRC = FtpGetFile(lngINetConn, "dirmap.txt", "c:\dirmap.txt", 0, 0, 1, 0)
'Send a file
Private Declare Function FtpPutFile Lib "wininet.dll" Alias "FtpPutFileA" _
( _
ByVal hFtpSession As Long, _
ByVal lpszLocalFile As String, _
ByVal lpszRemoteFile As String, _
ByVal dwFlags As Long, ByVal dwContext As Long _
) As Boolean
'ex: blnRC = FtpPutFile(lngINetConn, “c:\dirmap.txt”, “dirmap.txt”, 1, 0)
'Delete a file
Private Declare Function FtpDeleteFile Lib "wininet.dll" Alias "FtpDeleteFileA" _
( _
ByVal hFtpSession As Long, _
ByVal lpszFileName As String _
) As Boolean
'ex: blnRC = FtpDeleteFile(lngINetConn, “test.txt”)
modRegional:
Option Explicit
Private Declare Function GetLocaleInfo Lib "Kernel32" Alias "GetLocaleInfoA" (ByVal Locale As Long,
ByVal LCType As Long, ByVal lpLCData As String, ByVal cchData As Long) As Long
Private Declare Function SetLocaleInfo Lib "Kernel32" Alias "SetLocaleInfoA" (ByVal Locale As Long,
ByVal LCType As Long, ByVal lpLCData As String) As Boolean
Private Declare Function GetUserDefaultLCID% Lib "Kernel32" ()
Private Sub ChangeSettingExample() 'change the setting of the character displayed as the decimal
separator.
Call SetLocalSetting(LOCALE_SDECIMAL, ",") 'to change to ","
Stop 'check your control panel to verify or use the
GetLocaleInfo API function
Call SetLocalSetting(LOCALE_SDECIMAL, ".") 'to back change to "."
End Sub
Option Explicit
Private Declare PtrSafe Sub xLib "Kernel32" Alias "Sleep" (ByVal dwMilliseconds As Long)
Private Declare Sub apiSleep Lib "Kernel32" Alias "Sleep" (ByVal dwMilliseconds As Long)
#End If
The above declaration tells VBA how to call the function "Sleep" defined in file Kernel32.dll
Win64 and Win32 are predefined constants used for conditional compilation
Pre-defined Constants
Some compilation constants are already pre-defined. Which ones exist will depend on the bitness of the office
version you're running VBA in. Note that Vba7 was introduced alongside Office 2010 to support 64 bit versions of
Office.
The main difference when declaring APIs is between 32 bit and 64 bit Office versions which introduced new
parameter types (see Remarks section for more details)
Notes:
Declarations are placed at the top of the module, and outside any Subs or Functions
Procedures declared in standard modules are public by default
To declare a procedure private to a module precede the declaration with the Private keyword
DLL procedures declared in any other type of module are private to that module
start = Timer
Debug.Print "Paused for " & Format(Timer - start, "#,###.000") & " seconds"
End Sub
It is recommended to create a dedicated API module to provide easy access to the system functions from VBA
wrappers -- normal VBA Subs or Functions that encapsulate the details needed for the actual system call such as
parameters used in libraries, and initialization of those parameters
To declare a DLL procedure, add a Declare statement to the Declarations section of the code window.
Declare Function publicname Lib "libname" [Alias "alias"] [([[ByVal] variable [As type] [,[ByVal]
variable [As type]]...])] As Type
Declare Sub publicname Lib "libname" [Alias "alias"] [([[ByVal] variable [As type] [,[ByVal]
variable [As type]]...])]
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Also of note is that most invalid calls to the API's will crash Excel, and possibly corrupt data files
Private Declare Function system Lib "libc.dylib" (ByVal command As String) As Long
Sub RunSafari()
Dim result As Long
result = system("open -a Safari --args http://www.google.com")
Debug.Print Str(result)
End Sub
The examples bellow (Windows API - Dedicated Module (1 and 2)) show an API module that includes common
declarations for Win64 and Win32
Private Declare PtrSafe Function apiInternetOpen Lib "WiniNet" Alias "InternetOpenA" (ByVal
sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, ByVal sProxyBypass As
String, ByVal lFlags As Long) As Long 'Open the Internet object 'ex: lngINet =
InternetOpen(“MyFTP Control”, 1, vbNullString, vbNullString, 0)
Private Declare PtrSafe Function apiInternetConnect Lib "WiniNet" Alias "InternetConnectA"
(ByVal hInternetSession As Long, ByVal sServerName As String, ByVal nServerPort As Integer, ByVal
sUsername As String, ByVal sPassword As String, ByVal lService As Long, ByVal lFlags As Long, ByVal