In This Chapter
Previous chapters in this book examined Excel’s built-in worksheet functions and how you can use them to build more complex formulas. These functions provide a great deal of flexibility when creating formulas. However, you may encounter situations that call for custom functions. This chapter discusses why you may want to use custom functions, how you can create VBA Function procedures, and methods for testing and debugging them.
You are, of course, familiar with Excel’s worksheet functions. Even novices know how to use the most common worksheet functions, such as SUM, AVERAGE, and IF. Excel 2016 includes more than 450 predefined worksheet functions—everything from ABS to ZTEST.
You can use VBA to create additional worksheet functions, which are known as custom functions or user-defined functions (UDFs). With all the functions that are available in Excel and VBA, you may wonder why you would ever need to create new functions. The answer: to simplify your work and give your formulas more power.
For example, you can create a custom function that can significantly shorten your formulas. Shorter formulas are more readable and easier to work with. However, it’s important to understand that custom functions in your formulas are usually much slower than built-in functions. On a fast system, though, the speed difference often goes unnoticed.
The process of creating a custom function is not difficult. In fact, many people (these authors included) enjoy creating custom functions. This book provides you with the information that you need to create your own functions. In this and the next chapter, you’ll find many custom function examples that you can adapt for your own use.
Without further ado, we’ll show you a simple VBA Function procedure. This function, named USER, does not accept arguments. When used in a worksheet formula, this function simply displays the user’s name in uppercase characters. To create this function, follow these steps:
Start with a new workbook.
This is not really necessary, but keep it simple for right now.
Click your workbook’s name in the Project window.
If the Project window is not visible, press Ctrl+R to display it.
Type the following code into the code window:
Function USER() ' Returns the user's name USER = Application.UserName USER = UCase(USER) End Function
Figure 25.1 shows how the function looks in a code window.
To try out the USER function, activate Excel (press Alt+F11) and enter the following formula into any cell in the workbook:
=USER()
If you entered the VBA code correctly, the Function procedure executes, and your name displays (in uppercase characters) in the cell.
When Excel calculates your worksheet, it encounters the USER custom function and then goes to work following the instructions. Each instruction in the function is evaluated, and the result is returned to your worksheet. You can use this function any number of times in any number of cells.
This custom function works just like any other worksheet function. You can insert it into a formula by using the Insert Function dialog box. It also appears in the Formula AutoComplete drop-down list as you type it in a cell. In the Insert Function dialog box, custom functions appear (by default) in the User Defined category. As with any other function, you can use it in a more complex formula. For example, try this:
="Hello "&USER()
Or use this formula to display the number of characters in your name:
=LEN(USER())
If you don’t like the fact that your name is in uppercase, edit the procedure as follows:
Function USER() ' Returns the user's name USER = Application.UserName End Function
After editing the function, reactivate Excel and press F9 to recalculate. Any cell that uses the USER function displays a different result.
In this section, we discuss some of the technical details that apply to Function procedures. These are general guidelines for declaring functions, naming functions, using custom functions in formulas, and using arguments in custom functions.
The official syntax for declaring a function is as follows:
[Public | Private][Static] Function name ([arglist]) [As type] [statements] [name = expression] [Exit Function] [statements] [name = expression] End Function
The following list describes the elements in a Function procedure declaration:
Each function must have a unique name, and function names must adhere to a few rules:
Using a custom VBA function in a worksheet formula is like using a built-in worksheet function. However, you must ensure that Excel can locate the Function procedure. If the Function procedure is in the same workbook as the formula, you don’t have to do anything special. If it’s in a different workbook, you may have to tell Excel where to find it. You can do so in three ways:
Precede the function’s name with a file reference. For example, if you want to use a function called CountNames that’s defined in a workbook named Myfuncs.xlsm, you can use a formula like the following:
=MyFuncs.xlsm!CountNames(A1:A1000)
If you insert the function with the Insert Function dialog box, the workbook reference is inserted automatically.
Custom functions, like Excel’s built-in functions, vary in their use of arguments. Keep the following points in mind regarding VBA Function procedure arguments:
All cells and ranges that a function uses should be passed as arguments. In other words, a Function procedure should never contain direct references to cells or ranges.
Excel’s Insert Function dialog box is a handy tool that enables you to choose a particular worksheet function from a list of available functions. The Insert Function dialog box also displays a list of your custom worksheet functions and prompts you for the function’s arguments.
By default, custom functions are listed under the User Defined category, but you can have them appear under a different category. You also can add some text that describes the function.
When you select one of Excel’s built-in functions in the Insert Function dialog box, a brief description of the function appears (see Figure 25.3). You may want to provide such a description for the custom functions that you create.
Here’s a simple custom function that returns its argument, but with no spaces:
Function REMOVESPACES(txt) REMOVESPACES = Replace(txt, " ", "") End Function
The following steps describe how to provide a description for a custom function:
Activate Excel and choose Developer ➜ Code ➜ Macros (or press Alt+F8).
The Macro dialog box lists available Sub procedures but not functions.
Type the name of your function in the Macro Name box.
Make sure that you spell it correctly.
Click the Options button to display the Macro Options dialog box.
If the Options button is not enabled, you probably spelled the function’s name incorrectly.
Type the function description in the Description field (see Figure 25.4).
The Shortcut key field is irrelevant for functions.
Oddly, Excel does not provide a direct way to assign a custom function to a particular function category. If you want your custom function to appear in a function category other than User Defined, you need to execute some VBA code. Assigning a function to a category also causes it to appear in the drop-down controls in the Formulas ➜ Function Library group.
For example, assume that you created a custom function named REMOVESPACES, and you’d like this function to appear in the Text category (that is, Category 7) in the Insert Function dialog box. To accomplish this, you need to execute the following VBA statement:
Application.MacroOptions Macro:="REMOVESPACES", Category:=7
One way to execute this statement is to use the Immediate window in the VB Editor. If the Immediate window is not visible, choose View ➜ Immediate Window (or press Ctrl+G). Figure 25.5 shows an example. Just type the statement and press Enter. Then save the workbook, and the category assignment is also stored in the workbook. Therefore, this statement needs to be executed only one time. In other words, it is not necessary to assign the function to a new category every time the workbook is opened.
Alternatively, you can create a Sub procedure and then execute the procedure.
Sub AssignToFunctionCategory() Application.MacroOptions Macro:="REMOVESPACES", Category:=7 End Sub
After you execute the procedure, you can delete it. A function can be assigned to only one category. The last category assignment replaces the previous category assignment (if any).
You will, of course, substitute the actual name of your function, and you can specify a different function category. The AssignToFunctionCategory procedure can contain any number of statements—one for each of your functions.
Table 25.1 lists the function category numbers that you can use. Notice that a few of these categories (10–13) normally don’t display in the Insert Function dialog box. If you assign your function to one of these categories, the category appears.
Table 25.1 Function Categories
Category Number | Category Name |
0 | All (no specific category) |
1 | Financial |
2 | Date & Time |
3 | Math & Trig |
4 | Statistical |
5 | Lookup & Reference |
6 | Database |
7 | Text |
8 | Logical |
9 | Information |
10 | Commands |
11 | Customizing |
12 | Macro Control |
13 | DDE/External |
14 | User Defined |
15 | Engineering |
16 | Cube |
17 | Compatibility* |
18 | Web** |
*The Compatibility category was introduced in Excel 2010.
**The Web category was introduced in Excel 2013.
You can also create custom function categories. The statement that follows creates a new function category named My VBA Functions and assigns the REMOVESPACES function to this category:
Application.MacroOptions Macro:="REMOVESPACES", Category:="My VBA Functions"
When you use the Insert Function dialog box to enter a function, the Function Arguments dialog box displays after you click OK. For built-in functions, the Function Arguments dialog box displays a description for each of the function’s arguments.
In Chapter 26, we present a function named EXTRACTELEMENT:
Function EXTRACTELEMENT(Txt, n, Separator) As String ' Returns the nth element of a text string, where the ' elements are separated by a specified separator character Dim AllElements As Variant AllElements = Split(Txt, Separator) EXTRACTELEMENT = AllElements(n - 1) End Function
This function returns an element from a delimited text string and uses three arguments. For example, the following formula returns the string fghi (the third element in the string, which uses a dash to separate the elements):
=EXTRACTELEMENT("ab-cde-fghi-jkl", 3, "-")
Following is a VBA Sub procedure that adds argument descriptions, which appear in the Function Arguments dialog box:
Sub DescribeFunction() Dim desc(1 To 3) As String desc(1) = "The delimited text string" desc(2) = "The number of the element to extract" desc(3) = "The delimiter character" Application.MacroOptions Macro:="EXTRACTELEMENT", ArgumentDescriptions:=desc End Sub
The argument descriptions are stored in an array, and that array is used as the ArgumentDescriptions argument for the MacroOptions method. You need to run this procedure only one time. After doing so, the argument descriptions are stored in the workbook.
Naturally, testing and debugging your custom function is an important step that you must take to ensure that it carries out the calculation you intend. This section describes some debugging techniques you may find helpful.
VBA code that you write can contain three general types of errors:
An error in code is sometimes called a bug. Locating and correcting such an error is called debugging.
When you test a Function procedure by using a formula in a worksheet, you may have a hard time locating runtime errors because (unlike syntax errors) they don’t appear in a pop-up error box. If a runtime error occurs, the formula that uses the function simply returns an error value (#VALUE!). This section describes several approaches to debugging custom functions.
The MsgBox statement, when used in your VBA code, displays a pop-up dialog box. You can use MsgBox statements at strategic locations within your code to monitor the value of specific variables. The following example is a Function procedure that should reverse a text string passed as its argument. For example, passing Hello as the argument should return olleH. If you try to use this function in a formula, however, you see that it does not work—it contains a logical error:
Function REVERSETEXT(text) As String ' Returns its argument, reversed Dim TextLen As Long, i As Long TextLen = Len(text) For i = TextLen To 1 Step -1 REVERSETEXT = Mid(text, i, 1) & REVERSETEXT Next i End Function
You can insert a temporary MsgBox statement to help you figure out the source of the problem. Here’s the function again, with the MsgBox statement inserted within the loop:
Function REVERSETEXT(text) As String ' Returns its argument, reversed Dim TextLen As Long, i As Long TextLen = Len(text) For i = TextLen To 1 Step -1 REVERSETEXT = Mid(text, i, 1) & REVERSETEXT MsgBox REVERSETEXT Next i End Function
When this function is evaluated, a pop-up message box appears, once for each time through the loop. The message box shows the current value of REVERSETEXT. In other words, this technique enables you to monitor the results as the function is executed. Figure 25.6 shows an example.
The information displayed in the series of message boxes shows that the text string is being built within the loop, but the new text is being added to the beginning of the string, not the end. The corrected assignment statement is
REVERSETEXT = REVERSETEXT & Mid(text, i, 1)
When the function is working properly, make sure that you remove all the MsgBox statements.
To display more than one variable in a message box, you need to concatenate the variables and insert a space character between each variable. The following statement, for example, displays the value of three variables (x, y, and z) in a message box:
MsgBox x & " " & y & " " & z
If you omit the blank space, you can’t distinguish the separate values.
Alternatively, you can separate the variable with vbNewLine, which is a constant that inserts a line break. When you execute the following statement, x, y, and z each appear on a separate line in the message box:
MsgBox x & vbNewLine & y & vbNewLine & z
If you find that using MsgBox statements is too intrusive, another option is to insert some temporary code that writes values directly to the VB Editor Immediate window. (See the sidebar “Using the Immediate window“ later in this chapter.) You use the Debug.Print statement to write the values of selected variables.
For example, if you want to monitor a value inside a loop, use a routine like the following:
Function VOWELCOUNT(r) Dim Count As Long, Ch As String Dim i As Long Count = 0 For i = 1 To Len(r) Ch = UCase(Mid(r, i, 1)) If Ch Like "[AEIOU]" Then Count = Count + 1 Debug.Print Ch, i End If Next i VOWELCOUNT = Count End Function
In this case, the value of two variables (Ch and i) prints to the Immediate window whenever the Debug.Print statement is encountered. Figure 25.7 shows the result when the function has an argument of North Carolina.
When your function is debugged, make sure that you remove the Debug.Print statements.
Another way to test a Function procedure is to call the function from a Sub procedure. To do this, simply add a temporary Sub procedure to the module and insert a statement that calls your function. This is particularly useful because runtime errors display as they occur.
The following Function procedure contains a runtime error. As we noted previously, the runtime errors don’t display when you are testing a function by using a worksheet formula. Rather, the function simply returns an error (#VALUE!):
Function REVERSETEXT(text) As String ' Returns its argument, reversed Dim TextLen As Long, i As Long TextLen = Len(text) For i = TextLen To 1 Step -1 REVERSETEXT = REVERSETEXT And Mid(text, i, 1) Next i End Function
To help identify the source of the runtime error, insert the following Sub procedure:
Sub Test() x = REVERSETEXT("Hello") MsgBox x End Sub
This Sub procedure simply calls the REVERSETEXT function and assigns the result to a variable named x. The MsgBox statement displays the result.
You can execute the Sub procedure directly from the VB Editor. Simply move the cursor anywhere within the procedure and choose Run ➜ Run Sub/UserForm (or just press F5). When you execute the Test procedure, you see the error message that is shown in Figure 25.8.
Click the Debug button, and the VB Editor highlights the statement causing the problem (see Figure 25.9). The error message does not tell you how to correct the error, but it does narrow your choices. After you identify the statement that’s causing the error, you can examine it more closely, or you can use the Immediate window. See the sidebar “Using the Immediate window“ to help locate the exact problem.
In this case, the problem is the use of the And operator instead of the concatenation operator (&). The correct statement is as follows:
REVERSETEXT = REVERSETEXT & Mid(text, i, 1)
Another debugging technique is to set a breakpoint in your code. Execution pauses when VBA encounters a breakpoint. You can then use the Immediate window to check the values of your variables, or you can use F8 to step through your code line by line.
To set a breakpoint, move the cursor to the statement at which you want to pause execution and choose Debug ➜ Toggle Breakpoint. Alternatively, you can press F9 or click the vertical bar to the left of the code window. Any of these actions highlights the statement to remind you that a breakpoint is in effect. (You also see a dot in the code window margin.) You can set any number of breakpoints in your code. To remove a breakpoint, move the cursor to the statement and press F9. Figure 25.10 shows a Function procedure that contains a breakpoint.
If you create some custom functions that you use frequently, you may want to store these functions in an add-in file. A primary advantage to this is that you can use the functions in formulas in any workbook without a filename qualifier.
Assume that you have a custom function named ZAPSPACES and that it’s stored in Myfuncs.xlsm. To use this function in a formula in a workbook other than Myfuncs.xlsm, you need to enter the following formula:
=Myfuncs.xlsm!ZAPSPACES(A1:C12)
If you create an add-in from Myfuncs.xlsm and the add-in is loaded, you can omit the file reference and enter a formula like the following:
=ZAPSPACES(A1:C12)
Creating an add-in from a workbook is simple. The following steps describe how to create an add-in from a normal workbook file:
Activate the VB Editor and select the workbook in the Project window. Choose Tools ➜ xxx Properties and click the Protection tab (where xxx corresponds to the name of your project). Select the Lock Project for Viewing check box and enter a password (twice). Click OK.
You need to do this last step only if you want to prevent others from viewing or modifying your macros or custom dialog boxes.
Reactivate Excel. Choose File ➜ Info ➜ Properties ➜ Show Document Panel, and Excel displays its Document Properties panel above the Formula bar. Enter a brief, descriptive title in the Title field and a longer description in the Comments field.
This last step is not required, but it makes the add-in easier to use by displaying descriptive text in the Add-Ins dialog box.
Click Save.
A copy of the workbook is saved (with an .xlam extension), and the original macro-enabled workbook (.xlsm) remains open.
After you create your add-in, you can install it by using the standard procedure:
3.144.95.36