Archive for the ‘Microsoft Excel’ Category
Excel 2011 supports UDFs?
I had a chance (30 minutes) to play with Excel 2011 on the Mac OS X today. I was shocked to discover that Excel 2011 didn’t appear to support User-Defined Functions (UDFs) like Excel 2010 for Windows. My understanding was that this release would be one where it implemented Visual Basic for Applications (VBA) like Windows. Initially I thought it didn’t but I bought my own copy, did a full install with Visual Basic, and it appears that Microsoft has delivered. Oops, my bad for assuming the machine I played on originally had a solid installation. It appears to have had only a standard installation.
Watch out because UDFs fail with a #NAME!
error on a standard install of Excel 2011. While they’re found in the Insert Function dialog in both cases, they only appear to work with a full installation. The downside for Insert Function is that, like Excel 2008, it has no quick poplist to narrow the function choices to groups. We have the big list once more.
Here are my test functions:
Function hello() hello = "Hello World!" End Function Function hellowhom(nameIn As Variant) hellowhom = "Hello " + CStr(nameIn) + "!" End Function |
I think I found the trick to get Excel 2011 recognize and run User-Defined Functions. Make sure you do a custom installation and check Visual Basic for Application. Anyway, you can test these functions like that shown below. Column A contains the text of the formulas (a preceding single quote does that), and column B contains actual function calls.
Enabling the Developer ribbon took a few moments when I got my own copy. I figured that updating this was better than making a new post on the blog and linking them. It’s a three step process.
- Navigate to the Excel menu item and select Preferences…, as shown below.
- In the Excel Preferences shown below, click the Ribbon icon.
- In the Show or hide tabs, or drag them into the order you prefer: box shown below, enable the Developer checkbox.
It’s awesome, now accountants and economists can switch to Mac OS X without having to host a virtual machine with Microsoft Excel.
Excel Parameter Validation
While working on a VBA write-up for some documentation on Excel 2007/2010, I ran into some interesting parameter validation rules for Excel User-Defined Functions (UDFs). I found that optional values are suppressed when you pass a cell reference that points to an empty cell.
Let’s say you develop a simple test function like the following. You may expect that it returns the number passed as a call parameter or the default value of the opt
variable, 22
, but it doesn’t.
1 2 3 4 | Function OptionalOverride(Optional opt As Variant = 22) ' Return the unfiltered call parameter. OptionalOverride = opt End Function |
A call to an OptionalOverride
function like this works when the cell reference points to a value, but fails when it points to an empty cell. It returns a value of 0
with a numeric data type, as verified by the built-in TYPE
function.
=OptionalOverride(D1) |
This behavior means you always need to check for empty cell references and reassigns them a value inside an if-block. That is if you really want the default value to apply in all cases. The function includes the explicit call parameter assignment in the modified function.
1 2 3 4 5 6 7 8 | Function OptionalOverride(Optional opt As Variant = 22) ' Explicit assignments required when a cell reference points to an empty cell. If IsEmpty(opt) Then opt = 21 End If ' Return the unfiltered call parameter unless empty then return 21. OptionalOverride = opt End Function |
The modified function returns (a) the call parameter value, (b) 21 when the call parameter points to (references) an empty cell, (c) 22 when you exclude the variable from the call parameter list, or (d) any string value found in the call parameter. The return of a string value is clearly not the desired behavior.
You must modify the if-block by checking whether the call parameter is some data type other than a number before assigning a default value. The following demonstrates the final parameter validating function.
1 2 3 4 5 6 7 8 | Function OptionalOverride(Optional opt As Variant = 22) ' Explicit assignments required when a cell reference points to an empty cell. If IsEmpty(opt) Or Not IsNumeric(opt) Then opt = 21 End If ' Return the filtered call parameter. OptionalOverride = opt End Function |
Hope this helps a few folks trying to avoid that ugly #VALUE!
error returning from your UDFs.
LIKE operator in Excel
While discussing the limitations of wild card comparisons in the Microsoft Excel’s IF
function, I resolved to share my ISLIKE
function. It acts like the LIKE
operator in SQL. You can’t use the word like as the function name because it’s a reserved word in Visual Basic for Applications (VBA).
The function lets you compare any string to see if it is contained in another string. The default mode is case-insensitive, and the override mode case-sensitive. The optional third element lets you override the default comparison method.
You should remember or note that a number 0
is always evaluated as false, and any other number (negative or positive) is always true. You can put the ISLIKE
function in a macro sheet or an Add-in library. I recently posted instructions on how to create your own Add-in library.
Here is the VBA code for the user defined ISLIKE
function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | Function ISLIKE( inValue As Variant _ , hasValue As Variant _ , Optional caseSensitive As Integer = 0) ' Define a logical return variable and assign a default value. Dim logicReturn As Boolean logicReturn = False If caseSensitive Then ' Finds a case-insensitive value as a component in a larger value. If inValue Like "*" + hasValue + "*" Then logicReturn = True End If Else ' Finds a case-sensitive value as a component in a larger value. If UCase(inValue) Like "*" + UCase(hasValue) + "*" Then logicReturn = True End If End If ' Return the logical value. ISLIKE = logicReturn End Function |
Line numbers 12 and 19 use the "*"
, which is equivalent to the regular expression of "\.*"
. I’ve no idea why they made this choice, and it’s not well explained on Microsoft’s web site covering SmartTags.
You can shorten the program and make it slightly less efficient by performing the case sensitive match twice. The following doesn’t rely on nested block IF
statements. You also must change the third parameter to a Boolean
or the Not
operator won’t work correctly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | Function ISLIKE( lookupValue As Variant _ , containsValue As Variant _ , Optional caseSensitive As Boolean = 0) ' Define a logical return variable and assign a default value. Dim logicReturn As Boolean logicReturn = False ' Finds a case-insensitive value as a component in a larger value. If caseSensitive And _ lookupValue Like "*" + containsValue + "*" Then logicReturn = True ' Finds a case-sensitive value as a component in a larger value. ElseIf Not caseSensitive And _ UCase(lookupValue) Like "*" + UCase(containsValue) + "*" Then logicReturn = True End If ' Return the logical value. ISLIKE = logicReturn End Function |
Somebody asked me, “How this can be useful?” That meant from their perspective, how is it useful beyond a comparison of cell values. A quick example of that additional utility can be demonstrated from an early post. In that post on replacing a VLOOKUP
function with a UDF, there’s compound logic used to find the occurrence of a substring in a string. The ISLIKE
function can simplify that logic.
Here’s that snippet of the code from that referenced post. The snippet evaluates whether the base string is greater than one character and checks the second character of the string to see if it is a -
(minus sign).
58 | ElseIf Len(LetterGrades(i)) > 1 And Mid(LetterGrades(i), 2, 1) = "-" Then |
You can replace the compound logic with a call to the LIKE
function. It would replace line 58 as follows:
58 | ElseIf ISLIKE(LetterGrades(i),"-") Then |
The logic of the function examines to see if the first parameter contains the second parameter. This is what is meant by like. In the following illustration, you examine if a string contains a substring (or is like a substring). The cell A2
is also assigned a Range name. Range names can increase the readability of your models and functions. All but the last one perform case insensitive comparisons that find whether the substring in cell A2
is found with cell range of the B5:B10
. The B10
formula performs a case sensitive match.
Another logic comparison uses grades to see if a minus sign is found in them. The example compares the cell values in A1
against the and
A2B1
and B2
values. Returning true for one and false for the other cases in column C
of the respective rows.
Here are the calls to the ISLIKE
user defined function. The first one is case-insensitive and the second case-sensitive, but there’s no case sensitivity with a minus sign, -
.
Spreadsheet Functions | |
---|---|
Cell | Formula |
C1 | =ISLIKE(A1,B1) |
C2 | =ISLIKE(A2,B2,TRUE) |
As always, I hope this helps. If you’ve comments or alternative approaches, please let me know.
Excel dynamic ranges
Microsoft Excel has many Visual Basic for Application (VBA) features that are seemingly not well understood. One of these features is passing a range value into a VBA procedure (a Sub
) or function. The following example demonstrates how to pass a dynamic range to a local multiple dimensional array, and process the uploaded data in a VBA function.
My sample spreadsheet looks like the screen shot below. The formula call is in cell A11
and the text of the formula is in cell B11
.
I left a debug MsgBox()
call that demonstrates how you size a range. This shows the range above based on zero-based numbering, which means 5 rows are reported as 4 rows because 0 contains a row, and 3 columns are reported as 2 columns for the same reason. If the dialog looks strange to a Windows user, that’s because it’s one generated on a Mac OS X running Excel 2011. 🙂
I kept this as simple as possible to demonstrate the how to do this. Unfortunately, feedback required adding more extensive comments and making it a bit more bulletproof on concepts. The variable names were chosen to help read the syntax. Two loops are used to demonstrate (a) how you assign the range values to a multidimensional array, and (b) how you read the array values in your VBA code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | Function dynamicArray(lookupValue As String, table As Range) ' Define the row starting cell, Row n maps to the first row where the range is found in the spreadsheet. ' Row 1 is the first row and 1,048,576 is the last possible row. Dim rowStart As Integer Dim rowSize As Integer ' Define the column starting cell, Column A maps to 1, Column B maps to 2, et cetera. ' Column A is the first column and XFD is the last possible column. Dim columnStart As Integer Dim columnSize As Integer ' Create a dynamic multiple dimension array without any physical size. Dim multidimensionArray As Variant ' Define and declare a local returnValue variable. Dim returnValue As Boolean returnValue = False ' Assign the starting row and column values, and the length of values. ' Since you need to add the row and column to the starting grid coordinates, you need to use 0-based numbering, ' which means you subtract one from the length. rowStart = table.Row rowSize = table.Rows.Count - 1 columnStart = table.Column columnSize = table.Columns.Count - 1 ' This demonstrates that the range starts in the row and column, and ' the length and width of the multiple dimension array. ' ---------------------------------------------------------------------- ' Insert single quotes for the next two lines to suppress testing the program with variables. MsgBox ("(RowStart [" + CStr(rowStart) + "], (ColStart [" + CStr(columnStart) + "]) " + _ "(RowSize [" + CStr(rowSize) + "] ColSize [" + CStr(columnSize) + "])") ' ---------------------------------------------------------------------- ' Redimension the arrays maximum size, rows first, columns second. ReDim multidimensionArray(rowSize, columnSize) ' Read through the range and assign it to a local and dynamically sized array variable. ' An important note to those unfamilar with the Cells function, it works on the active worksheet and uses two ' parameters, the absolute row and column number or relative row and column numbers. In this sample, the ' easiest solution is to use absolute row an dolumn numbers. For i = 0 To rowSize For j = 0 To columnSize multidimensionArray(i, j) = CStr(Cells(rowStart + i, columnStart + j)) Next j Next i ' Read through the local variable range and view the content set. For i = 0 To rowSize For j = 0 To columnSize ' Check if the lookupValue has been found and return true. If lookupValue = CStr(multidimensionArray(i, j)) Then returnValue = True Exit For End If Next j Next i ' Return a Boolean value: true when found and false when not found. dynamicArray = returnValue End Function |
You would call it with syntax like that below. The range can be any valid range value in Excel, which is any two cell references separated by a colon.
=dynamicArray("LookupString",A1:G5) |
As always, I hope this helps others looking for a way to perform this task natively in Microsoft Excel. As an addendum, I thought the absence of a code example for VB on the MSDN web page was quite interesting.