Getting ready to code
First you’ll need to open Excel Options from the main menu, go to Popular and enable
the Developer tab.
Once you’ve done that, click the Developer tab in the ribbon, and click the Visual Basic button on
the left to open the VBA editor. To make formulas that can be used by the whole workbook,
you’ll want to place your code in the Modules folder in the left-hand pane.
Excel specifics
Creating Normal and Array formulas
To create a formula which returns a single value, create a function thus:
Public Function MyFormula() As Variant
MyFormula = 123
End Function
The Variant specifier is the type of value to return. Variant essentially means any type; if you
want to limit the formula to return a specific type, you can change it, eg.:
Public Function MyFormula() As Currency
Public Function MyFormula() As String
VBA in Excel provides a raft of types such as Currency which are tailored to typical Excel usage
scenarios.
As you can see above, you set the result (return value) of the formula by assigning it to the
function name itself, ie.
MyFormula = 123
when the function is named MyFormula.
If you are making an array formula, you will need to return several values and they must be of
the Variant type:
Public Function MyArrayFormula() As Variant()
Dim Result(1 To 2) As Variant
Result(1) = 123
Result(2) = 456
MyArrayFormula = Result
End Function
Notice that the function declaration uses parentheses (Variant()) after the type specifier to
denote that we will be returning multiple values. By changing the value after To in
the Dimstatement, you can specify how many values to return, and of course, when you place
the array formula in the spreadsheet, you must ensure you select the same number of cells as
there are returned values otherwise an error will occur.
Placing the formula in the spreadsheet
Using the code above, you can enter text such as =MyFormula() into a cell to use the formula
you have created.
To place an array formula:
1. Select the cells you want to use for the results
2. Type in the formula
3. Press CTRL+SHIFT+ENTER
Passing values to a formula
The input value the formula accepts go in the function definition. For example, to pass a string:
Public Function MyFormula(ByVal someText As String) As Variant
To use multiple inputs, separate them with commas:
Public Function MyFormula(ByVal someText As String, ByVal somePrice As
Currency) As Variant
Notice that you can use cell references when you place the formula, eg. =MyFormula(A2, A3)and
they will be converted to a String and a Currency in the example above. If the cell values cannot
be converted, a type mismatch error will occur. If you don’t care what type of data is in the
cells, use Variant:
Public Function MyFormula(ByVal firstItem As Variant, ByVal secondItem As
Variant) As Variant
Passing cell references
Use the Range type to pass a reference to a single cell or a rectangular selection of cells
(including single rows and columns):
Public Function MyFormula(SomeCell as Range) As Variant
Note that you do not use ByVal here as we want the cell reference directly.
Getting the value of a cell reference
If your Range variable refers to a single cell, use the Value property to fetch its contents:
Public Function Add(firstCell As Range, secondCell As Range) As Variant
Add = firstCell.Value + secondCell.Value
End Function
Defining cell references in code
Define ranges as follows:
Dim myRange As Range
Set myRange = Range("A1:Z100")
Notice this will refer to the currently active worksheet (see below for how to access any
worksheet). Also note that we use the Set statement here as is required by VBA when setting
the value of an object rather than a simple type such as Integer.
Getting the values of all the cells in a range
You can iterate over all the cells in a range like this:
Dim singleCell as Range
For Each singleCell In MyRange
(do something with singleCell.Value)
Next singleCell
Referring to specific worksheets
Use the Sheets object to access a named worksheet:
Dim myRange As Range
Set myRange = Sheets("Some Worksheet").Range("A1:Z100")
You can also use the shortcut syntax:
Dim myRange As Range
Set myRange = Range("'Some Worksheet'!A1:Z100")
Referring to named ranges
This is very simple!
Dim myRange As Range
Set myRange = Sheets("My Worksheet").Range("SomeNamedRange")
Referring to tables and table columns
The header row of a table defines the column names, so if you have a table called Productswith
a column called Price you can fetch all the prices (all the cells in the Price column of the table) as
follows:
Dim myRange As Range
Set myRange = Sheets("My Worksheet").Range("Products[Price]")
Note the use of square brackets to denote that the column is part of a table. To fetch the whole
table:
Dim myRange As Range
Set myRange = Sheets("My Worksheet").Range("Products")
Note: You can name a table in the Design tab of the ribbon when any cell in the table is
selected.
Working with row and column values
Oftentimes you will want to retrieve the actual row and column numbers of a cell or the start of
a range so that you can refer to adjacent cells. To kick off, if you have a row and column number
already and want to get the cell reference, use:
Dim someCell As Range
Set someCell = Cells(rowNumber, columnNumber)
If you just want the cell’s actual value, things are simpler:
cellValue = Cells(rowNumber, columnNumber).Value
Note that cells are numbered from (1,1) as the top-left corner of a worksheet. In fact, all
operations in Excel VBA are 1-indexed (meaning that iterating over a group of items starts at 1,
not 0 as is the case in many other languages).
To get the row and column numbers of the first item in a range (which could be a single cell, a
single row, a single column or a rectangular selection), use the Row and Column properties
of Range:
someColumn = Sheets("My Worksheet").Range("SomeNamedRange").Column
someRow = someOtherRange.Row
Note that when you retrieve these row and column numbers, they are relative to the top-left of
the entire worksheet, not the range. Fortunately, this is usually what you want.
Retrieving cell references from a position relative to another cell reference
Armed with this information, we can now look at the cells around a cell reference. Suppose we
have a formula where we pass a single cell reference from part of a table, perhaps a product
name, and we want to look up the price of the product. We can find out the row number of the
product, and – if we use a named range – the column number of the price column. Using these
two values together, we can find the price of the product:
My Function doSomethingWithPrice(ProductNameCell As Range) As Variant
ProductRow = ProductNameCell.Row
PriceColumn = Range("ProductTable[Price]").Column
Price = Cells(ProductRow, PriceColumn).Value
...
End Function
Notice that it’s essential to pass a cell reference and not the actual product name value here; if
we did so, there would be no way to find out which cell was passed to the formula.
Accessing built-in worksheet functions
Use the WorksheetFunction object:
someCount = WorksheetFunction.CountIf(Range("someRange"),
someCell.Value)
This is the equivalent of writing the following formula in a cell:
=CountIf(someRange, 123)
storing the result in someCount.
Any worksheet function can be used, but as you can see above, the arguments must be passed
in terms of VBA cell references and values.
Exiting a formula function early
Sometimes you will want to do a test before continuing with the formula, and exit with a
different value if the test succeeds or fails. You can use the Exit Function statement to achieve
this. For example:
Public Function yesOrNo(someCell As Range) As String
If someCell.Value <> 123 Then
yesOrNo = "No"
Exit Function
End If
... do something else ...
yesOrNo = "Yes"
End Function
This function exits early if the value of the passed cell is not 123, otherwise it continues
executing the formula.
Language elements and syntax
Comparisons in VBA for C++/C#/PHP/Java programmers
For equality comparisons, use = instead of ==.
For inequality comparisons, use <> instead of !=.
For compound comparisons, use And and Or instead of && and ||.
For boolean comparisons, use True and False instead of true, false and !.
If statements do not require outer brackets in VBA. Compound If statements take the form:
If someCondition = someOtherCondition And ... And ... Then
some code
ElseIf anotherCondition <> yetAnotherCondition Then
some code
Else
some code
End If
Ternary operator
There is no equivalent of the ternary operator in VBA, however you can easily replicate it by
using a function such as:
Public Function iff(condCheck As Boolean, ifTrue As Variant, ifFalse As
Variant) As Variant
If condCheck Then
iff = ifTrue
Else
iff = ifFalse
End If
End Function
Use like this:
ternaryResult = iff(1+1 = 2, "1+1 does equal 2", "1+1 does not equal
2")
Note that by using Variant in the function definition, this ternary operator workaround will
automatically work with any types.
Switch statements
VBA provides Select Case as the equivalent of switch. The syntax is as follows:
Select Case someValue
Case 1
do something
Case 2 To 5
do something else
Case Else
same as default in other languages (if no case matches)
End Select
Note that no break statement is required, there is no case fall-through in VBA like there is in
C++ etc.
Comments
Use the apostrophe symbol to add comments to your code:
' this line will be ignored
Code that spans more than one line
Use the underscore character to split code onto more than one line:
If 1+1 = 2 _
And 2+2 = 4 Then
some code
End If
String functions
Here are a few useful string functions:
string1 & string2 ' concatenates two strings together
Len(stringValue) ' gets the length in characters of a string
Left$(stringValue, qty) ' get the left-hand most qty characters of a string
Mid$(stringValue, mid, qty) ' get a sub-string starting at character mid and qty characters long (
indexed in VBA)
Right$(stringValue, qty) ' get the right-hand most qty characters of a string
Trim(stringVal) ' trims the leading and trailing whitespace from a string
Replace(stringVal, src, dst) ' returns stringVal with all occurrences of src replaced with dst. Th
same length
LCase(stringVal) ' returns stringVal converted to lowercase
UCase(stringVal) ' returns stringVal converted to uppercase
Join(stringArray) ' takes an array of strings and combines them in order into a single string (like
And a couple of more esoteric examples:
Asc(charValue) ' get the ASCII code of a character
Hex(num) ' get a string representation of the hexadecimal value of a number
There are of course, many others.
Error handling
Error handling is surprisingly important when dealing with Excel formulas. If you have a formula
that is called frequently and keeps failing, you can end up with memory leaks, crashed
spreadsheets and ultimately your whole computer grinding to a halt.
There are several error handling statements and they can be placed anywhere in a function to
change the currently active error handling scheme. Here is a common use case:
Public Function myFormula() As Variant
' do something at the start of the function
On Error GoTo SomethingWentWrong:
' try to do something that might cause an error
On Error GoTo 0
' rest of function
myFormula = 123
Exit Function
SomethingWentWrong:
' exit function gracefully
myFormula = 0
End Function
The statement On Error GoTo SomethingWentWrong: indicates to VBA that if an error occurs in
the code that follows, execution should jump to the specified label (SomethingWentWrong). Note
the use of the colon at the end of this statement.
Once the potentially problematic code is executed, the line On Error GoTo 0 returns Excel to its
default error handling, which essentially returns a formula error (#VALUE) if something goes
awry in your function.
After calculating the formula result, we add in Exit Function to prevent the error handling code
from being executed if everything went well. Finally, at the end of the function, we define
the SomethingWentWrong label and set the formula result to some reasonable value if there was
a problem.
This tactic will prevent you from getting #VALUE errors where a formula cannot be calculated,
which prevents column sub-totalling and PivotTables etc. from working properly.
Useful patterns
Finding the number of occurrences of a single value in a range of cells
If myRange is your range and testValue is the value you want to look for:
Qty = 0
Dim singleCell As Range
For Each singleCell In myRange
If singleCell.Value = testValue Then
Qty = Qty + 1
End If
Next singleCell
Qty now contains the number of items in myRange which have the value testValue.
You could just use WorksheetFunction.CountIf(myRange, testValue) to do the same thing, but
what if you want to get the row numbers of each occurrence of testValue? Instead, you can do
this:
Dim matchingRows(1 To 100) As Integer
Qty = 0
Dim singleCell As Range
For Each singleCell In myRange
If singleCell.Value = testValue Then
Qty = Qty + 1
matchingRows(Qty) = singleCell.Row
End If
Next singleCell
matchingRows now contains the row numbers of each occurrence of testValue in myRange. This
cannot be done with a standard worksheet function.
Finding all the unique values in a range
We can use the Scripting.Dictionary Visual Basic object to make things easier here:
Set UniqueValues = CreateObject("Scripting.Dictionary")
UniqueCount = 0
Dim singleCell As Range
For Each singleCell In myRange
If Not UniqueValues.Exists(singleCell.Value) Then
UniqueCount = UniqueCount + 1
UniqueValues.Add singleCell.Value, UniqueCount
End If
Next singleCell
Working with a disparate collection of cells
If the cells you want to test aren’t in a contiguous range, you will need to store the row and
column numbers of each cell to test. You can either use two one-dimensional arrays: one for the
rows and one for the column, or you can use an array of ranges containing a single cell each.
Let us suppose we want to test a number of disparate cells in a single column, whose column
number is stored in TestColumn. We have an array of the row numbers of each cell to test in this
column, stored in TestRows. You can iterate over all the cell values as follows:
Dim rowNumber As Integer
For Each rowNumber In TestRows
itemValue = Cells(rowNumber, TestColumn).Value
' do something with itemValue
Next rowNumber
Merging two or more ranges
You can merge various ranges together. Let us suppose you have an array of ranges
in rangeArray and you want to merge them into a single range. You can do this as follows:
Dim combinedRange As Range
For Each singleRange In rangeArray
If combinedRange Is Not Nothing Then
Set combinedRange = Application.Union(combinedRange, singleRange)
Else
Set combinedRange = singleRange
End If
Next singleRange
WARNING: Combined ranges may not always work as you expect. If the ranges are contiguous,
everything will be fine. However, you cannot merge a group of randomly placed cells this way
and then iterate over the range correctly. This is because when you reference a range, it is
always from the top-left cell. Consider the range B4:B6. This is a range with a starting row of 4,
starting column of 2, and a size (count) of 3. When you iterate over the 3 items, you correctly
get references to cells B4, B5 and B6 respectively. Now consider the case where you have
used Union to merge 3 disparate cells: C5, D8 and H4. The range has a starting row of 5,
starting column of 3 (column C) and a count of 3. It is only these starting values that the range
actually references, so if you iterate over it, you will retrieve cells C5, C6 and C7! Be wary of this
and use the techniques further above of storing the row and column numbers directly in an array
if you need to iterate over non-contiguous arbitrary groups of cells.
Fetching a web page
It’s very handy to be able to download web pages in a macro, usually for the purpose of parsing
them to insert data based on the values input to the formula. For example you might use the
input value to execute a web search to fetch the price of a product.
The business of just fetching a page is fairly straightforward. You create an invisible instance of
Internet Explorer (one that doesn’t appear on the screen), request a page, wait for the download
to complete, fetch the document from the browser then close it down:
Public Function FetchWebPage() As Boolean
Set IE = CreateObject("InternetExplorer.Application")
IE.Visible = False
FetchURL = "http://www.somewebsite.com/somepage"
IE.Navigate2 FetchURL
Do While IE.ReadyState <> 4 Or IE.Busy
DoEvents
Loop
On Error GoTo PageLoadFailed:
Set pageContents = IE.Document
On Error GoTo 0
' do something with pageContents
FetchWebPage = True
IE.Quit
Exit Function
PageLoadFailed:
FetchWebPage = False
IE.Quit
End Function
The Do While loop polls Internet Explorer repeatedly to see if the page has loaded yet or not,
relinquishing control to Windows periodically to do other tasks while waiting with
the DoEventscall.
The error handling is extremely important here. You must ensure that IE is closed down
regardless of the success or failure of your function to execute without error, otherwise you will
end up with loads of invisible copies of IE (use Task Manager to close them if necessary) which
will grind your machine to a halt as it consumes all of the available memory. It can also make
Excel stall or become extremely unresponsive. Always call IE.Quit on all execution paths of your
function.
Passing a search query to a web page
Things get a little more tricky if you need to pass a string as a GET query, because the string
must be processed such that any characters that would normally be invalid in a URL are properly
encoded. This is called, quite simply, URL encoding. Here is a function I pilfered from somewhere
on the internet which does the job:
Public Function URLEncode( _
StringVal As String, _
Optional SpaceAsPlus As Boolean = False _
) As String
Dim StringLen As Long: StringLen = Len(StringVal)
If StringLen > 0 Then
ReDim Result(StringLen) As String
Dim i As Long, CharCode As Integer
Dim Char As String, Space As String
If SpaceAsPlus Then Space = "+" Else Space = "%20"
For i = 1 To StringLen
Char = Mid$(StringVal, i, 1)
CharCode = Asc(Char)
Select Case CharCode
Case 97 To 122, 65 To 90, 48 To 57, 45, 46, 95, 126
Result(i) = Char
Case 32
Result(i) = Space
Case 0 To 15
Result(i) = "%0" & Hex(CharCode)
Case Else
Result(i) = "%" & Hex(CharCode)
End Select
Next i
URLEncode = Join(Result, "")
End If
End Function
If you have a product name you wish to search for, for example, you can change the
way FetchURL is defined in the previous example as follows:
FetchURL = "http://www.somewebsite.com/search?product=" &
URLEncode(productName)
If you need to send multiple variables, use URLEncode on each of them like this:
FetchURL = "http://www.somewebsite.com/search?product=" & URLEncode(productName) & "&lang
URLEncode(pageLanguage)
Parsing a web page
In simple cases you can use the Get External Data From Web feature in the Data tab of the
ribbon to automatically do web page parsing for you and avoiding using VBA altogether. In other
cases you may be able to access the data as XML rather than an HTML web page; this solution is
preferred and in these cases you should use the XML map tool to import your data.
In the cases where you want to parse an HTML page programmatically in a formula, or the
rudimentary parsing provided by Excel isn’t sufficient and no XML feed is available, you can as
with other languages use the DOM object model to parse the page. It is beyond the scope of this
article to explain the DOM but for those who are familiar with it or don’t mind diving into the
deep end, here are a few quick and dirty examples.
To get an element by its ID:
Set myElement = IE.Document.GetElementById("id-to-find")
To get a list of elements by class name:
Set myElements = IE.Document.GetElementsByClassName("class-name-to-
find")
To get a list of elements by tag name:
Set tableRowItems = someTableRowElement.GetElementsByTagName("td")
To iterate over a list of elements and get their text values:
For Each tableItem In tableRowItems
tableItemValue = tableItem.innerText
Next tableItem
To access a specific element in a list of elements (for example a list of elements found by tag or
class name) without using For Each, you can use the Item collection. Consider an unordered list
(<ul>) with a class name of list-class. Several lists in the document may have the list-
class class, but you just want to find the 2nd occurrence in the document and fetch all the list
items (<li>) from it. You can do so as follows:
Set listItems = IE.Document.GetElementsByClassName("list-
class").Item(1).GetElementsByTagName("li")
Note here that the elements are 0-indexed, so the first list-class element can be found
in Item(0) etc.
To access the child elements (excluding whitespace) of an element, use
the Childrencollection. For example if you have an element myTableRow which points to
a <tr> and you want to get the third <td> element text from the row:
tableItemValue = myTableRow.Children(2).innerText
Note here that the elements are 0-indexed, so the first </td> can be found in Children(0) etc.
Conclusion
I failed to find a web page which summarized the key facts on how to get simple tasks done in
Excel VBA without having to Google almost every line of code I wrote, so I thought it would be
nice to pull it all together in one place. I hope you found it useful!
Got questions about Excel VBA? Post them below and I’ll expand this resource with solutions.