Writing Your Own Functions in VBA
Writing Your Own Functions in VBA
Design And Development
Search The Site: Search
>
Writing Your Own Functions In VBA
This page describes how to write your own worksheet functions in VBA.
While Excel provides a plethora of builtin functions, especially so if you include functions in the Analysis Took
Pack (in Excel 2007, the functions that used to be in the ATP are now native Excel functions) you may find it
useful to create your own custom function for things that Excel cannot (easily) do with the builtin functions.
While it takes longer for Excel to calculate a VBA function than it does to calculate a worksheet formula, all else Stone
being equal, the flexibility of VBA often makes a VBA function the better choice. The rest of this page assumes
that you are familiar with the basics of VBA programming. Production
Simple User Defined Functions Line
A User Defined Function (or UDF) is a Function procedure that typically (but not necessarily) accepts some
inputs and returns a result. A UDF can only return a value to the cell(s) whence it was called it must not
modify the contents or formatting of any cell and must not modify the operating environment of Excel. If you
Notable Stone
attempt to change anything, the function will terminate immediately and return a #VALUE error to the calling cell. Production Line.
In Excel 97 and 2000, a UDF cannot use the Find method of a Range object, even though that method does Building High-
not change anything in Excel. This was fixed with Excel 2002. quality Mining
The following is an example of a simple UDF that calculates the area of a rectangle:
Brand.
Function RectangleArea(Height As Double, Width As Double) As Double
RectangleArea = Height * Width
End Function
This function takes as inputs two Double type variables, Height and Width, and returns a Double as its result.
Once you have defined the UDF in a code module, you can call it from a worksheet cell with a formula like:
www.sinoftm.com
=RectangleArea(A1,B1)
where A1 and B1 contain the Height and Width of the rectangle.
Because functions take inputs and return a value, they are not displayed in the list of procedures in the Macros
dialog.
Where To Put The Code
The code for a UDF should be placed in a standard code module, not one of the Sheet modules and not in the
ThisWorkbook module. In the VBA editor, go to the Insert menu and choose Module. This will insert a new
code module into the project. A module can contain any number functions, so you can put many functions into a
single code module. You can change the name of a module from Module1 to something more meaningful by
pressing the F4 key to display the Properties window and changing the Name property to whatever you want.
You can call a function from the same workbook by using just the function name. For example:
=RectangleArea(12,34)
It is possible, but strongly recommended against, to have two functions with the same name is two separate
code modules within the same workbook. You would call them using the module name from cells with formulas
like:
=Module1.MyFunction(123)
=Module2.MyFunction(123)
Doing this will lead only to confusion, so just because it is possible doesn't mean you should do it. Don't do it.
Do not give the same name to both a module and a function (regardless of whether that module contains that
function). Doing so will cause an untrappable error.
You can call a UDF that is contained in another (open) workbook by using the workbook name in the formula.
For example,
='MyBook.xls'!RectangleArea(A1,A2)
will call the function RectangleArea defined in the workbook MyBook.xls . If a function is defined in an Add
In (either an XLA or an Automation AddIn; see this page for information about writing Automation AddIns in
VB6), you don't need to include the name of the AddIn file. The function name alone is sufficient for calling a
function in an AddIn.
CAUTION: Excel does not handle well the case when a workbook contains a function with the same name as a
function in an AddIn. Suppose both Book1.xls and MyAddIn.xla have a function named Test defined as:
Function Test() As String
Test = ThisWorkbook.Name
End Function
The function Test in each workbook simply returns the name of the workbook in which the code resides, so the
function Test defined in Book1.xls returns the string "Book1.xls" and the function Test defined in
MyAddIn.xla returns the string "MyAddIn.xla". In Book1.xls , enter the formula =Test() in cell A1 and
enter the formula =MyAddin.xla!Test() in cell A2. The functions will work properly when you first enter the
formulas, but if you edit the formula in A2 (e.g., select the cell, then press the F2 key followed by the ENTER
key), the name Test is recognized as a function in Book1.xls so Excel will change the function call in cell A2
from =MyAddIn.xla!Test() to simply =Test(), and this will call the function Test from Book1.xls not
MyAddIn.xla . This will almost certainly return an incorrect result. This problem occurs only when the
workbook and an AddIn both have a function with the same name. It does not occurs if two workbooks have
functions with the same name. This has not been fixed in Excel 2007.
UDFs And Calcuations
As a general rule, you should pass into the function all the values it needs to properly calculate the result. That
means that your UDF should not make explicit refences to other cells. If you reference other cells directly from
within the function, Excel may not recalculate the function when that cell is changed. For example, a poorly
written UDF is as follows:
Public Function BadRectangleArea(Height As Double) As Double
BadRectangleArea = Height * Range("A1").Value
End Function
In this function, the Width is assumed to be in cell A1. The problem here is that Excel doesn't know that this
function depends on cell A1 and therefore will not recalculate the formula when A1 is changed. Thus, the cell
calling the function call will not contain the correct result when cell A1 is changed. You can force Excel to
recalculate a UDF whenever any calculation is made by adding the line
Application.Volatile True
as the first line in the function. For example,
Function BadRectangleArea(Height As Double) As Double
Application.Volatile True
BadRectangleArea = Height * Range("A1").Value
End Function
This has the drawback, however, that the function is recalculated even if it doesn't need to be recalculated,
which can cause a performance problem. In general, you shouldn't use Application.Volatile but instead
design your UDF to accept as inputs everything it needs to properly caclulate the result.
Returning Arrays From Functions
See the Returning Arrays From User Defined Functions page for information about returning arrays as the result
of your User Defined Function.
Returning Errors From Functions
You can return an error value from a UDF if an incorrect input parameter is passed in. To do this, the function
must return a Variant data type and use the CVErr function to create an errortype Variant result. For
example, the function Divide below will return a #DIV/0 error if the divisor is 0.
Function Divide(A As Double, B As Double) As Variant
If B = 0 Then
Divide = CVErr(xlErrDiv0)
Else
Divide = A / B
End If
End Function
You can use any of the following error constants with the CVErr function to return an error to Excel:
xlErrDiv0 for a #DIV/0 error
xlErrNA for a #N/A error
xlErrName for a #NAME? error
xlErrNull for a #NULL error
xlErrNum for a #NUM error
xlErrRef for a #REF error
xlErrValue for a #VALUE error
If any other value is passed to CVErr , Excel will treat it as a #VALUE error. It is generally good practice to validate the input parameters
and return an error value with CVErr rather than letting the VBA code error out with #VALUE errors. If a runtime error occurs in your
code, or you attempt to change anything in Excel, such other cells, VBA terminates the function and returns a #VALUE error to Excel.
Determining The Range From Which Your UDF Was Called
Under nearly all circumstances, it is not necessary to know the actual address of the range from which your
UDF was called. Indeed, you should avoid have the need for such information. Your function should work the
same regardless of where it was called from. However, you may well need to know the size of the range from
which your UDF was called if it was array entered into a range of cells. The Application.Caller object will
return a reference to the range from which your function was called, regardless of whether that range is a single
cell or a range of cells.
CAUTION: Application.Caller will be a Range object only when the function in
which it appears was called from a worksheet cell. If the function was called from another
VB procedure, Application.Caller will be an Errortype Variant and most any
attempt to use it will result in a Type Mismatch (13) error. If the code containing
Application.Caller was called via the OnAction property of a Shape object on a
worksheet, Application.Caller will be a String containing the name of the sheet.
Therefore, if your function might be called from another VB procedure rather than only
from a worksheet cell, you should test Application.Caller with the IsObject
function to ensure that it is indeed an object before attempting to access any of its
properties.
CAUTION: In Excel 2003, a new object, Application.ThisCell , was introduced. It is
similar in nature to Application.Caller , but differs when a UDF is array entered into
a range of more than one cell. Application.Caller will return the a Range reference
to the entire range in which the UDF was arrayentered. Application.ThisCell
returns a reference to the first (upper left) cell in the range from which the UDF was
called. Frankly, I'm not sure why Application.ThisCell was introduced in the first
place.
You can get the properties of Application.Caller with code like the following:
Function Test()
Dim CallerRows As Long
Dim CallerCols As Long
Dim CallerAddr As String
With Application.Caller
CallerRows = .Rows.Count
CallerCols = .Columns.Count
CallerAddr = .Address
End With
Test = 1234
End Function
Using A Variable Number Of Parameters
You can define a function to accept a variable number of parameters in one of two somewhat different ways.
You can use a specified number of optional parameters, or you can allow the function to accept any number of
parameters, including none at all, using a ParamArray Variant parameter. The two methods are mutually
exclusive. You cannot use both optional parameters and a ParamArray in the same function.
Optional Variant Parameters
You can define one or more parameters as Optional Variant types. For example:
Function OptParam(D As Double, Optional B As Variant) As Variant
If IsMissing(B) = True Then
OptParam = D
Else
If IsNumeric(B) = True Then
OptParam = D + B
Else
OptParam = CVErr(xlErrNum)
End If
End If
End Function
This function defines the parameter B as an optional Variant and uses the IsMissing function to determine
whether the parameter was passed. The IsMissing function can be used only with Variant type parameters.
If IsMissing is used with any other data type (e.g., a Long), it will return False. More than one parameter
may be Optional , but those parameters must be the last parameters accepted by the function. That is, once
one parameter is specified as Optional , all the parameters that follow it must also be optional. You cannot
have a required parameter following an optional parameter. If a parameter is declared as Optional but is not a
Variant (e.g, it is a String or a Long) and that parameter is omitted, the IsMissing function will return
False and the default value for that data type (0 or empty string) will be used. You can specify a default value
for an optional parameter that should be used if the parameter is omitted. For example, the parameter B in the
function below is optional with a default value of 2.
Function FF(A As Long, Optional B As Long = 2) As Variant
If B = 0 Then
FF = CVErr(xlErrDiv0)
Else
FF = A / B
End If
End Function
In this code, the value 2 is used for the default value of B if B is omitted. When using a default value for a parameter, you don't call the
IsMissing function. Your code should be written to use either a passed in parameter value or the default value of the parameter. With
the code above, the following two worksheet functions are equivalent:
=FF(1,2)
=FF(1)
Variant ParamArray
The second method for working with optional parameters is to use a ParamArray Variant parameter. A
ParamArray allows any number of parameters, including none at all, to be passed to the function. You can
have one or more required parameters before the ParamArray , but you cannot have any optional parameters if
you have a ParamArray . Moreover, the ParamArray variable must be the last parameter declared for a
function. The ParamArray variables must be Variant types. You cannot have a ParamArray of other
types, such as Long integers. If necessary, you should validate the values passed in the ParamArray , such
as to ensure they are all numeric. If your function requires one or more inputs followed by a variable number of
parameters, declare the required parameters explicitly and use a ParamArray only for the optional parameters.
For example, the function SumOf below accepts any number of inputs and simply adds them up:
Function SumOf(ParamArray Nums() As Variant) As Variant
''''''''''''''''''''''''''''''''''
' Add up the numbers in Nums
''''''''''''''''''''''''''''''''''
Dim N As Long
Dim D As Double
For N = LBound(Nums) To UBound(Nums)
If IsNumeric(Nums(N)) = True Then
D = D + Nums(N)
Else
SumOf = CVErr(xlErrNum)
Exit Function
End If
Next N
SumOf = D
End Function
In your function code, you can use:
Dim NumParams As Long
NumParams = UBound(Nums) LBound(Nums) + 1
to determine how many parameters were passed in the ParamArray variable Nums . This will be 0 if no parameters were passed as the
ParamArray . Of course, the code above counts the number of parameters within the ParamArray , not the total number of
parameters to the function.
See Optional Paramateres To Procedures for a more in depth discussion of Optional parameters and
ParamArray parameter type.
Returning Arrays From Functions
Your function can return an array of values so that it can be entered as an array formula, either entered into an
array of cells or to return an array to be aggregated by a function like SUM. (See this page for a discussion of
Array Formulas.) The NumsUpTo function below returns an array of the integers from 1 to the input parameter L.
For simplicity, L must be between 1 and 5. The function also requires that if the function is array entered, it
must be in either a single row or a single column. A range with more than one row and more than one column
will result in a #REF error. This restriction applies to this example only; it is not a limitation on UDF array
functions in general. See the next section for example code that return values to a two dimensional range of
cells.
In a UDF, Application.Caller returns a Range type object that references the cell(s) from which the
formula was called. Using this, we can test whether we need a row array or a column array. If the function is
called from a column of cells (e.g., array entered into A1:A5), the VBA array must be transposed before
returning it to Excel. Note that there is also an object named Application.ThisCell that references the cell
from which a function is called. In functions called from a single cell, Application.Caller and
Application.ThisCell work the same. However, they differ when a function is called as an array formula.
You should use Application.Caller , not Application.ThisCell .
Function NumsUpTo(L As Long) As Variant
''''''''''''''''''''''''''''''''''''
' Add up the integers from 1 To L.
''''''''''''''''''''''''''''''''''''
Dim V() As Long
Dim ArraySize As Long
Dim N As Long
Dim ResultAsColumn As Boolean
''''''''''''''''''''''''''''''''''''
' Allow inputs only between 0 and 5.
''''''''''''''''''''''''''''''''''''
If (L > 5) Or (L < 1) Then
NumsUpTo = CVErr(xlErrValue)
Exit Function
End If
''''''''''''''''''''''''''''''''''''
' Allow only one columns or one row.
''''''''''''''''''''''''''''''''''''
If Application.Caller.Rows.Count > 1 And _
Application.Caller.Columns.Count > 1 Then
NumsUpTo = CVErr(xlErrRef)
Exit Function
End If
'''''''''''''''''''''''''''''''''''''''
' Test whether the result should be
' returned as a columns or row array.
'''''''''''''''''''''''''''''''''''''''
If Application.Caller.Rows.Count > 1 Then
ResultAsColumn = True
Else
ResultAsColumn = False
End If
''''''''''''''''''''''''''''''
' ReDim the array to hold L elements.
''''''''''''''''''''''''''''''
ReDim V(1 To L)
''''''''''''''''''''''''''''''
' Fill up the array
''''''''''''''''''''''''''''''
For N = 1 To UBound(V)
V(N) = N
Next N
'''''''''''''''''''''''''''''''''
' Return the result, transposing
' if necessary.
'''''''''''''''''''''''''''''''''
If ResultAsColumn = True Then
NumsUpTo = Application.Transpose(V)
Else
NumsUpTo = V
End If
End Function
If the SumUpTo function is called from a range that has more than one row, the array must be transposed before
it is returned, using the Application.Transpose function. The result of the function is an array of L integers
from 1 to L. If the range from which the function is called has N cells, and N is less than L (the size of the result
array), elements at the end of array are discarded and only the firt N elements are sent to the cells. If L is less
than N (the function is entered into an array of cells larger than L), #N/A errors fill out the ending elements of
the range on the worksheet. Since the result of NumsUpTo is an array, it can be used in an array formula, such
as
=SUM(NumsUpTo(5))
which returns 15, the sum of the numbers from 1 to 5.
Returning Arrays With Two Dimensions
To return an array to a range that contains more than one row and more than one column, create a two
dimensional array with the first dimension equal to the number of rows in the range and the second dimension
equal to the number of columns in the range. Then load that array, looping through the rows and columns and
then return the array as the result.
The function AcrossThenDown below loads the calling cells with sequential integers, moving across each row
and then moving down to the next row. The function DownThenAcross below loads the calling cells with
sequential integers, moving down each column then moving right to the next column. The difference between
the two function is in the For loops, whether the outer loop is for Rows or Columns. As noted before, use
Application.Caller not Application.ThisCell to get a reference to the range of cells calling the
function.
Function AcrossThenDown() As Variant
Dim NumCols As Long
Dim NumRows As Long
Dim RowNdx As Long
Dim ColNdx As Long
Dim Result() As Variant
Dim N As Long
''''''''''''''''''''''''''''''''''''''''''''
' Get the number of rows and columns in the
' range that is calling this function.
''''''''''''''''''''''''''''''''''''''''''''
NumCols = Application.Caller.Columns.Count
NumRows = Application.Caller.Rows.Count
''''''''''''''''''''''''''''''''''''''''''''
' ReDim the Result array to the number
' of rows and columns in the calling range.
''''''''''''''''''''''''''''''''''''''''''''
ReDim Result(1 To NumRows, 1 To NumCols)
For RowNdx = 1 To NumRows
For ColNdx = 1 To NumCols
N = N + 1
Result(RowNdx, ColNdx) = N
Next ColNdx
Next RowNdx
AcrossThenDown = Result
End Function
Function DownThenAcross() As Variant
Dim NumCols As Long
Dim NumRows As Long
Dim RowNdx As Long
Dim ColNdx As Long
Dim Result() As Variant
Dim N As Long
''''''''''''''''''''''''''''''''''''''''''''
' Get the number of rows and columns in the
' range that is calling this function.
''''''''''''''''''''''''''''''''''''''''''''
NumCols = Application.Caller.Columns.Count
NumRows = Application.Caller.Rows.Count
''''''''''''''''''''''''''''''''''''''''''''
' ReDim the Result array to the number
' of rows and columns in the calling range.
''''''''''''''''''''''''''''''''''''''''''''
ReDim Result(1 To NumRows, 1 To NumCols)
For ColNdx = 1 To NumCols
For RowNdx = 1 To NumRows
N = N + 1
Result(RowNdx, ColNdx) = N
Next RowNdx
Next ColNdx
DownThenAcross = Result
End Function
This page last updated: 1Sept2007
Created By Chip Pearson at Pearson Software Consulting
This Page: www.cpearson.com/excel/writingfunctionsinvba.aspx Email: [email protected]
Last Updated: 06Nov2013 Please read this page before emailing me.
Copyright 1997 2014, Charles H. Pearson Phone: (816) 3259822 USA Central Time (6:00 UTC)
Site Last Updated: 10Nov2016 Between 9:00 AM and 7:00 PM
Essential Tools For Developers
The world's choice for creating NETbased Commercial Quality AddIns for Office
AddIn Express Is The Most Important Tool For Creating Commerical Level Components
Learn more about Excel and VBA (Visual Basic for Applications).
Cite this page as:
Source: www.cpearson.com/excel/writingfunctionsinvba.aspx Copyright 2013, Charles H. Pearson Citation Information
This site created with Microsoft Visual Studio 2013 Premium and ASP.NET 4
Advertise Your Product On This Site