Running Excel's OFFSET using WorkSheetFunction causes error 438 - vba

My main goal is to create a named range that automatically resizes.
A common way to do this is to use the OFFSET formula in Excel and dynamically determine the height and/or width parameter values using CountA. I would like to do this using VBA. For simplicity purposes, I took a step back and focused just on getting OFFSET to run and return a range. I have the following code:
Public Function testfn()
Dim r As Range
On Error Resume Next
Set r = WorksheetFunction.Offset(Worksheets("MAIN").Range("H85"), 0, 0, 1, 2)
Debug.Print Err.Number
End Function
The output in the console is 438. I'm not really sure what's causing it because I have specified the starting reference explicitly and the rest of the values are hard-coded integers.
Can you please point out what exactly is causing the error? Could it be that it's not actually possible to call this function using VBA?

To mimic =OFFSET(rng, offR, offC, sizeR, sizeC) you can use either or both of:
rng.Offset(offR, offC)
and
rng.Resize(sizeR, sizeC)
or combined:
rng.Offset(offR, offC).Resize(sizeR, sizeC)
Since you're not actually offsetting here, you only need:
Set r = Worksheets("MAIN").Range("H85").Resize(1, 2)

This should work:
Set r = Worksheets("MAIN").Range("H85").Resize(1, 2)

Error 438 stands for: Object doesn't support this property or method.
(It might be worth to comment out the On Error Resume Next when you are debugging in order to have this information.)
This means that the method Offset is not supported by the object WorksheetFunction. This can be a little surprising since it does not appear on the list of functions that are not available in VBA according to Microsoft (See list).
However, it is indeed not available since it's not part of the methods listed here:
https://msdn.microsoft.com/en-us/library/office/ff822194(v=office.14).aspx (Office 2010)
https://msdn.microsoft.com/en-us/vba/excel-vba/articles/worksheetfunction-object-excel (Office 365)
Alternatives:
As already suggested by Tim Williams, using the Resize method of the Range Object is the easiest way to do what you are trying to do, but if you prefer to use the Offset method, you could use:
Dim r As Range, s As Range
Set s = Worksheets("MAIN").Range("H85")
Set r = Range(s, s.Offset(0, 1))
or alternatively, you can use the Evaluate function like this:
Set r = Application.Evaluate("Offset('MAIN'!H5, 0, 0, 1, 2)")
But this one would be a little bit slower.

Related

Magic braces in VBA Excel

I wrote some function to add some polylines to Excel sheet. Then I've discovered strange braces behavior.
I declare and define points array as this:
Dim points As Variant
Dim sh As Shape
points = Array(Array(10.5, 10.5), Array(20.4, 20.4), Array(5.1, 30.3), Array(10.5, 10.5))
' These both do not work and I get error about wrong type (error 1004) in 2007
' and application defined error 1004 on 2010:
ActiveWorkbook.ActiveSheet.Shapes.AddPolyline points
Set sh = ActiveWorkbook.ActiveSheet.Shapes.AddPolyline(points)
' These work fine:
ActiveWorkbook.ActiveSheet.Shapes.AddPolyline (points)
Set sh = ActiveWorkbook.ActiveSheet.Shapes.AddPolyline((points))
What is the strange magic of VBA braces?
Tested in 2007 and 2010 versions.
The additional parentheses around points cause the argument to be evaluated as an expression and consequently be passed ByVal.
The act of evaluating an array can change exactly how the data is packed inside the Variant that contains it (e.g. see VBA: Remove duplicates fails when columns array is passed using a variable as an example), and if the called procedure is not very lenient about what types of arrays it can accept (which it should be), then it will raise an error.
In your case I am actually surprised that passing an evaluated (points) even works, because the documentation mentions that a 2D array of Singles is expected, and Array(Array(...), Array(...), ...) is a jagged array as opposed to a 2D array. It would appear AddPolyline is written to cope with jagged arrays too, but it only recognizes them when the Variant containing the array has a particular set of flags in it which evaluating seems to produce (e.g. it might be that the presence or absence of VT_BYREF trips its flag comparison so it fails to recognize the passed array as supported).
I would call it a bug in AddPolyline, and I would explicitly define and fill a 2D array of Single to avoid it:
Dim points(1 To 4, 1 To 2) As Single
points(1, 1) = 10.5: points(1, 2) = 10.5
points(2, 1) = 20.4: points(2, 2) = 20.4
points(3, 1) = 5.1: points(3, 2) = 30.3
points(4, 1) = 10.5: points(4, 2) = 10.5

defining code on vba excel to simplify code writing process

I am attempting to reduce the amount of clutter on my code by creating "shortcuts" if you will
For instance, I always have to type
ThisWorkBook.ActiveSheet.Range
Is there a way for me to define the above to create a less wordy macro? I have tried convert to range and string and the former returns an error (but I could still get intellisense recognize and attempt to autofill) while the string version doesnt work.
Just like in any programming language, you can use variables to store data
For example:
Dim myrange As Range: Set myrange = Sheets("Sheet1").Range("B5")
Alternatively, if you will be working with the same object multiple times, you can use the With keyword
For example. instead of writing you want to work with table every time on every new line you can do
With Sheets("Sheet1").ListObjects("Table1")
.ListRows.Add
.ListColumns(2).Range(3) = "Hello World!"
' ... and so on
End With
Also, please on a sidenote: Avoid using Select/ActiveSheet/ActiveWorkbook and so on!
More info on how to here
You can create functions or customized properties, which are always evaluated when called
Property Get pARng As Range
Set pARng = ThisWorkBook.ActiveSheet.Range
End Property
Function fARng As Range
Set fARng = ThisWorkBook.ActiveSheet.Range
End Function
'Usage
Sub xxx
'...
pARng.Rows(1).Activate
'Same as ThisWorkBook.ActiveSheet.Range.Rows(1).Activate
fARng.Rows(1).Activate
'using function instead achieves same result
End Sub

Excel VBA Match if value is null

I have a piece of code within a larger script that simply allocates a value to "i" based on a match. The idea being I want it to give the match value if the value entered is found, or 0 if not. If it's 0, I can then exit sub with a message to the user. However, any time the match finds a null value, it just kills the sub, instead of it being handled as part of the iferror I've introduced. I've tried various manners of checking (using iif(iserror) for example) but none seem to work.
Code causing the issue is below:
i = Application.WorksheetFunction.IfError(Application.WorksheetFunction.Match(username, EL.Range("A:A"), 0), 0)
i is dim as an integer
Username is dim as a string, and comes from an inputbox
EL is dim as a worksheet, and contains the correct info.
This has absolutely no issues if I introduce any name that exists, it only fails as soon as I input a name that does not work, and I'm sort of stumped as to why. I see no reason for it to fail, but feel like I'm missing something simply and in-my-face.
Use this instead. Using Application instead of WorksheetFunction enables the error to be trapped and tested.
i = Application.IfError(Application.Match(UserName, EL.Range("A:A"), 0), 0)
I would use a slightly different approach to trap an error on Application.Match function:
Dim i As Variant
i = Application.Match(UserName, EL.Range("A:A"), 0)
' if Match wasn't able to found a "match"
If IsError(i) Then i = 0

VBA UDF fails to call Range.Precedents property

I am trying to write a simple UDF, which is supposed to loop through each of the cells of a given_row, and pick up those cells which do not have any precedent ranges, and add up the values.
Here is the code:
Public Function TotalActualDeductions(given_row As Integer) As Integer
Total_Actual_Deduction = 0
For present_column = 4 To 153
Set precedent_range = Nothing
If Cells(2, present_column) = "TDS (Replace computed figure when actually deducted)" Then
On Error Resume Next
Set precedent_range = Cells(given_row, present_column).Precedents
If precedent_range Is Nothing Then
Total_Actual_Deduction = Total_Actual_Deduction + Cells(given_row, present_column)
End If
End If
Next
TotalActualDeductions = Total_Actual_Deduction
End Function
If I try to run it by modifying the top declaration, from:
Public Function TotalActualDeductions(given_row As Integer) As Integer
to:
Public Function TotalActualDeductions()
given_row = 4
then it runs successfully, and as expected. It picks up exactly those cells which match the criteria, and all is good.
However, when I try to run this as a proper UDF from the worksheet, it does not work. Apparently, excel treats those cells which have no precedents, as though they have precedents, when I call the function from the worksheet.
I don't know why this happens, or if the range.precedents property is supposed to behave like this.
How can this be fixed?
After a lot of searching, I encountered this:
When called from an Excel VBA UDF, Range.Precedents returns the range and not its precedents. Is there a workaround?
Apparently, "any call to .Precedents in a call stack that includes a UDF gets handled differntly".
So, what I did was use Range.Formula Like "=[a-zA-Z]" , because it satisfied my simple purpose. It is in no way an ideal alternative to the range.precedents property.
Foe a more detailed explanation, please visit that link.

How does the VBA immediate window differ from the application runtime?

I've encountered a very strange bug in VBA and wondered if anyone could shed some light?
I'm calling a worksheet function like this:
Dim lMyRow As Long
lMyRow = WorksheetFunction.Match(vItemID, rngMyRange.Columns(1), 0)
This is intended to get the row of the item I pass in. Under certain circumstances (although I can't pin down exactly when), odd things happen to the call to the Match function.
If I execute that line in the immediate window, I get the following:
lMyRow = WorksheetFunction.Match(vItemID, rngMyRange.Columns(1), 0)
?lMyRow
10
i.e. the lookup works, and lMyRow gets a value assigned to it. If I let that statement execute in the actual code, I lMyRow gets a value of 0.
This seems very odd! I don't understand how executing something in the immediate window can succeed in assigning a value, where the same call, at the same point in program execution can give a value of 0 when it runs normally in code!
The only thing I can think of is that it's some odd casting thing, but I get the same behaviour taking if the variable to which I'm assigning is an int, a double, or even a string.
I don't even know where to begin with this - help!!
The only difference between the immediate window and normal code run is the scope.
Code in the immediate window runs in the current application scope.
If nothing is currently running this means a global scope.
The code when put in a VBA function is restricted to the function scope.
So my guess is that one of your variables is out of scope.
I would put a breakpoint in your function on that line and add watches to find out which variable is not set.
And if you don't have Option Explicit at the top of your vba code module, you should add it.
You're not assigning the function name so that function will always return zero (if you're expecting a Long). It seems you should have
makeTheLookup = lMyRow
at the end of your function.
I don't know if you are still looking at this or not but I would have written it this way:
Function makeTheLookup(vItemID As Variant, rngMyRange as Range)as Long
makeTheLookUp = WorksheetFunction.Match(vItemID, rngMyRange.Columns(1), 0)
End Function
I cannot reproduce the problem with Excel 2007.
This was the code I used:
Sub test()
Dim vItemID As Variant
Dim lMyRow As Long
Dim rngMyRange As Range
Set rngMyRange = ActiveWorkbook.Sheets(1).Range("A1:Z256")
vItemID = 8
lMyRow = WorksheetFunction.Match(vItemID, rngMyRange.Columns(1), 0)
Debug.Print lMyRow
End Sub
It may sound stupid but are you sure that all parameters of the Match function are the same in your macro and in the immediate window? Maybe the range object has changed?
Thanks for the answers guys - I should have been slightly more specific in the way I'm making the call below:
Function makeTheLookup(vItemID As Variant, rngMyRange as Range)
Dim lMyRow As Long
lMyRow = WorksheetFunction.Match(vItemID, rngMyRange.Columns(1), 0)
End Function
The odd thing is, I'm passing the two parameters into the function so I can't see any way they could be different inside and outside of the function. That said, I'm still entirely clueless as to what's causing this, particularly since it's a really intermittent problem
Is there any easy way of comparing the range object in the function context to the range object in the Immediate window context and telling if they're different? Given that range is a reference type, it feels like I should just be able to compare two pointers, but I've got no idea how to do that in VBA!
I'm using Excel 2007 by the way, although I'm not sure if that makes any difference.
As will has mentioned above, It most definitely seems like a problem of scope. Or an Obscure Bug.
I would try to use Debug.print statements, as well as Watch, and see if they match up, and take it from there.