What data type does find method return? - vba

I am trying to search 3 columns contained in the range specified below. I have spent a long time looking online and I have found similar questions but when I apply it to mine I am not getting the correct answer. So far (from another questions answer) I have:
Dim result As Range
Set result = range("DailyTable[[AmtNumberOut]:[AMTOutstanding]]").find(What:="#N/A Requesting Data...", After:=ActiveCell, LookIn:= _
xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:= _
xlNext, MatchCase:=False, SearchFormat:=False)
If result Is Nothing Then
ElseIf IsEmpty(result.Offset(0, 2)) Then
MsgBox ("Please wait for information to finish downloading. Import cancelled.")
Exit Sub
Else
MsgBox ("Please wait for information to finish downloading. Import cancelled.")
Exit Sub
End If
I understand that find returns nothing if no value is found which can cause issues with data types but I have tried defining result as Range, Object, Variant, String and all give mismatch error. I also read somewhere that it can return Boolean outputs (which did not work either). I have spent a long time stuck on a simple problem and I would be very grateful for any answers!
Thanks.

In your find you have
After:=ActiveCell,
This throws a mismatch error if your active cell is not in the table you reference, "DailyTable".
Also, your logic will have the message box appear if the offset is empty or not. So if Result is nothing, then nothing, but if the offset is empty or not, you get the message. As long as that's what you want, then why are you checking the offset to be empty?

Range.Find returns a Range object reference, initialized or not, period. If Range.Find found anything, you get a Range object reference, otherwise you get Nothing, which is an uninitialized object reference that will make your code throw run-time error 91 "object reference not set" if you try to make member calls against it.
I have tried defining result as Range, Object, Variant, String and all give mismatch error.
You want a Range. Object would work, however it would make all member calls made against it late-bound, i.e. resolved at run-time - you'd get no IntelliSense or auto-completion, and VBA would happily compile a typo regardless of whether Option Explicit is specified.
Variant would work as well, but again you'd lose IntelliSense, and you'd be wrapping the object reference into a Variant/Object, which incurs overhead you don't need.
String is dangerous. First, for it to even have any chance to work you need to drop the Set keyword. And then this is what you're really doing, with both the implicit default member access and implicit type conversion made explicit:
Dim result As String
result = CStr(Range(...).Find(...).Value)
The implicit default member call .Value is illegal against Nothing in the first place, and then there's no guarantee that the CStr string conversion will succeed: if the cell contains an error value (e.g. #N/A), then this is where you're getting a type mismatch run-time error, because an Error value can't be implicitly (or explicitly) converted to a String (or anything, actually).
If you're getting a type mismatch with result As Range, then you're doing something with the returned Range object's Value (implicitly or explicitly) and that value is an Error - you can't compare an error value against any type. You need to wrap whatever it is you're doing with it, with If IsError(result) Then - but it looks like none of the code you posted would cause a type mismatch error with Dim result As Range, assuming .Offset(0,2) isn't an error value either.

Related

Is there a range version of IsNumeric just like VBA function HasFormula?

I know that range().HasFormulareturns True only when every cell in the range has formula, otherwise it can return False or Null (when mixed). But there's no function like HasNumber. So to check if a range only contains number, I have to do
Dim all_numeric As Boolean
all_numeric = True
For Each cell In Range()
If (Not IsNumeric(cell)) Or IsEmpty(cell) Then 'I also want to get rid of empty cell
all_numeric = False
Exit For
End If
Next cell
Besides, there's WorksheetFunction.IsNumber that does similar thing but still needs a loop through the range. I am not sure if this can be very slow if the range contains a lot of numbers. And I wonder if there's any better way to check numeric values over a range object in VBA.
Maybe all_numeric = (r.Cells.Count - Application.Count(r)) = 0 (where r is a Range object)? – YowE3K 35 mins ago
That's indeed beautiful: it leverages Excel's own function that returns the number of numeric values in a range to determine the result:
WorksheetFunction.Count
Counts the number of cells that contain numbers and counts numbers within the list of arguments.
https://msdn.microsoft.com/en-us/library/office/ff840324.aspx
Error cells and empty cells aren't counted, which fulfills your requirement of not counting empty cells.
That makes a nice UDF to expose in a standard module I find:
'#Description("Returns True if all cells in specified range have a numeric value.")
Public Function IsAllNumeric(ByVal target As Range) As Boolean
IsAllNumeric = target.Cells.Count - Application.WorksheetFunction.Count(target) = 0
End Function
Note that I've used Application.WorksheetFunction.Count, not Application.Count:
The latter is a late-bound call that makes the VBA runtime work much harder than it needs to, to find the Count method. You're working on an extended COM interface, so you don't have compile-time validation either: Application.IDontExist compiles perfectly fine, and blows up with run-time error 438. As with any other late-bound member call, the VBE's IntelliSense can't help you with the parameters:
The former is an early-bound function call, which VBA resolves at compile-time. You're working on the WorksheetFunction interface directly, so the VBE gives you autocomplete and IntelliSense for the parameters.
Autocomplete:
IntelliSense:
The fact that the call is early-bound means no run-time overhead, therefore better performance - even if it ends up being the exact same internal Excel function that executes.
The drawback (if it's one at all) is that the late-bound Application.SomeFunction stuff is compatible with Excel4Macros, the old legacy pre-VBA way of automating Excel. So instead of raising run-time errors like their early-bound counterparts, the late-bound functions return error values such that you can should test them with IsError before you can assume what type you're actually getting.
With an early-bound WorksheetFunction.SomeFunction call, if the result Excel would display is #REF! or #VALUE!, or #N/A or whatever other possible error value, then you'll never be caught with a type mismatch run-time error for treating an error value as a String or a Long, or any other non-error VBA type. Instead, you just handle run-time errors, as you would with any other VBA API function call.
The late-bound call propagates an error value into your code; the early-bound call fails early: there could be 20 lines of code between where a cell value is read and where that value is used in a way that assumes there's no error value, and that instruction throws a type mismatch - and then you need to debug to trace back to the function that returned an error. With the early-bound code the function itself throws the error, so you don't have to dig it up.

VBA - getting column # with Find(), get error 91 - object var. or with block var. not set

I have a VBA script wherein I am trying to use find() to get the column number of a column on a separate worksheet (same workbook).
So, from "Sheet 1" I run this macro, to find the column on "Sheet 2" that has the word "Ins Val" in row 1. What's odd is that earlier in the macro, I use the same formula to get a column number without issue. However, the below code throws a "Run Time Error 91, Object Variable or With Block Not Set" but I can't figure why.
dim useDataWS as Worksheet, typeValColumn as Integer, theType as String, mainWS as Worksheet
Set mainWS = worksheets("Sheet 1")
Set useDataWS = worksheets("Sheet 2")
theType = mainWS.Cells(49,5).Value
'' the below line gives the error
typeValColumn = useDataWS.rows(1).Find(what:=theType, LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False).Column
But, earlier in that macro, I do the same thing- with no error :?
With useDataWS
noOneCol = .Rows(1).Find(what:=theType, LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False).Column
End With
Note: Even if I change that first part to use "with", the same error occurrs.
Any ideas? (Note: It's a more robust script, but I tried to get the parts that apply. If there's something else that might be causing this, let me know what other kinds of things I'm doing that could mess this up and I'll post more of the code).
edit: Thanks #Branislav Kollár - using search by "xlFormulas" instead of "xlValues" solves the issue. (Although I still don't know why Excel throws the error with 'values', but hey - it works!)
The reason, why the following line
typeValColumn = useDataWS.rows(1).Find(what:=theType, LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False).Column
was giving an error is because the Find()method found Nothing, and applying .column to a Nothing results in error.
Solution can be replacing LookIn:=xlValues with LookIn:=xlFormulas.
I'd like to provide more info about why it does work, but I don't know honestly. One thing I found out is that LookIn:=xlFormulas will find even hidden cells and I guess it has more general usage.
More about Find() method can be found on MSDN Range.Find Method, or Find Method in Excel VBA or .Find and .FindNext in Excel VBA
Sometimes it is better to revert back to the native worksheet functions. While .Find is better for a worksheet-wide search, both .CountIf and .Match can easily locate a value in a single row or column. I typically use .CountIf to see if the value if there and .Match to retrieve the column index number.
with useDataWS
if cbool(application.countif(.rows(1), theType)) then
typeValColumn = application.match(theType, .rows(1), 0)
else
debug.print "not found in row 1"
end if
end if
You can also check for the value's existence with If IsError(app.Match(...)) but I prefer the two-function approach. If you can write a MATCH worksheet function that does not return #N/A then the above code will locate theType.
This is not so much an answer as a quick diagnostic tool. You swear that Ins Val is in row 1 of Sheet 2. Put this formula in the cell directly below the one containing Ins Val.
=CODE(MID(H$1, ROW(1:1), 1))
Adjust H$1 for the cell that actually contains Ins Val and fill down for at least 9 rows.
Excel 2013 addendum:
Excel 2013 introduced the UNICODE function that may be more appropriate for this purpose.
=UNICODE(MID(H$1, ROW(1:1), 1))
        
If you are receiving anything other than the results shown above, then you have rogue characters in the cell value.
The easiest one-off solution is to retype the value. If this is a repeated import then other methods need to be in place to compensate. The best method is to adjust the import at the source but that is not always possible. The worksheet function CLEAN does a reasonable job of removing rogue characters but it is not the best. Sometimes custom routines need to be written that address specific circumstances.

Run time error '1004' Unable to get the Match propertyof the WorksheetFunction class

In my macro, I have the following code :
i = Application.WorksheetFunction.Match(str_accrual, Range(Selection, Selection.End(xlToRight)), 0)
where 'str_accrual' is a string captured earlier to this line and the Range selected is in a single row say from "A1" to "BH1" and the result will be a number which is the position of that string in that range selected.
When I run the macro, I get the error:
Run time error '1004' Unable to get the Match propertyof the WorksheetFunction class
But when I run the macro line by line using (F8) key, I don't get this error but when I run the macro continuously I get the error. Again, if the abort the macro and run it again the error doesn't appear.
I tried several times. It seems that if there is no match, the expression will prompt this error
if you want to catch the error, use Application.Match instead
Then you can wrap it with isError
tons of posts on this error but no solution as far as I read the posts. It seems that for various worksheet functions to work, the worksheet must be active/visible. (That's at least my latest finding after my Match() was working randomly for spurious reasons.)
I hoped the mystery was solved, though activating worksheets for this kind of lookup action was a pain and costs a few CPU cycles.
So I played around with syntax variations and it turned out that the code started to work after I removed the underscore line breaks, regardless of the worksheet being displayed. <- well, for some reason I still had to activate the worksheet :-(
'does not work
'Set oCllHeader = ActiveWorkbook.Worksheets("Auswertung").Cells(oCllSpielID.Row, _
Application.Match( _
strValue, _
ActiveWorkbook.Worksheets("Auswertung").Range( _
oCllSpielID, _
ActiveWorkbook.Worksheets("Auswertung").Cells(oCllSpielID.Row, lastUsedCellInRow(oCllSpielID).Column)), _
0))
'does work (removed the line breaks with underscore for readibility) <- this syntax stopped working later, no way around activating the worksheet :-(
Set oCllHeader = ActiveWorkbook.Worksheets("Auswertung").Cells(oCllSpielID.Row, Application.Match(strValue, ActiveWorkbook.Worksheets("Auswertung").Range(oCllSpielID, ActiveWorkbook.Worksheets("Auswertung").Cells(oCllSpielID.Row, lastUsedCellInRow(oCllSpielID).Column)), 0))
In the end I am fretting running into more realizations of this mystery and spending lots of time again.
cheers
I was getting this error intermittently. Turns out, it happened when I had a different worksheet active.
As the docs for Range say,
When it's used without an object qualifier (an object to the left of the period), the Range property returns a range on the active sheet.
So, to fix the error you add a qualifier:
Sheet1.Range
I had this issue using a third-party generated xls file that the program was pulling from. When I changed the export from the third-party program to xls (data only) it resolved my issue. So for some of you, maybe there is an issue with pulling data from a cell that isn't just a clean value.
I apologize if my nomenclature isn't great, just a novice to this.
That is what you get if MATCH fails to find the value.
Try this instead:
If Not IsError(Application.Match(str_accrual, Range(Selection, Selection.End(xlToRight)), 0)) Then
i = Application.Match(str_accrual, Range(Selection, Selection.End(xlToRight)), 0)
Else
'do something if no match is found
End If
Update
Here is better code that does not rely on Selection except as a means of user-input for defining the range to be searched.
Sub Test()
Dim str_accrual As String
Dim rngToSearch As Range
str_accrual = InputBox("Search for?")
Set rngToSearch = Range(Selection, Selection.End(xlToRight))
If Not IsError(Application.Match(str_accrual, rngToSearch, 0)) Then
i = Application.Match(str_accrual, rngToSearch, 0)
MsgBox i
Else
MsgBox "no match is found in range(" & rngToSearch.Address & ")."
End If
End Sub
I used "If Not IsError" and the error kept showing. To prevent the error, add the following line as well:
On Local Error Resume Next
when nothing is found, Match returns data type Error, which is different from a number. You may want to try this.
dim result variant
result = Application.Match(....)
if Iserror(result)
then not found
else do your normal thing

Returning the Cell location of specific value in a separate Workbook

To clarify. I have 1 spreadsheet I am creating the VBA in. When the Macro runs, I want the code to find a specific value in a separate WorkBook sheet and return the location of the value in the other WorkBook.
My initial thought was to use HLookUp, however that will only return the value, not the location of the value. My next thought was to use the Find function, but I can't get it to run. The error I get is: Run-time error "438': Object doesn't support this property or method
startValue = enrollBook.Sheets("Pop-FY").Range("D:Z"). _
Applications.WorksheetFunction. _
Find(What:=FYString, LookIn:=xlValues)
enrollBook is the other workbook.
startValue is supposed to be the location of the found value in the other spreadsheet
This is a weird way of calling a function & formatting you've got there, I first even thought that code was invalid.
First, you are confusing Range.Find with Application.WorksheetFunction.Find. You need the Range one, but are calling the other one.
Second, the error is because it's Application, not Applications.
Third, you will need a Set:
Set startValue = enrollBook.Sheets("Pop-FY").Range("D:Z").Find(What:=FYString, LookIn:=xlValues)
You mean something like this?
Dim sTest As String
Dim oRange As Range
Set oRange = Worksheets(1).Range("A1:Z10000").Find("Test", lookat:=xlPart)
MsgBox oRange.Address
I tested this, but you need to change your parameters.

VBA: How to get the last used cell by VBA code when the last error occured in a Workbook/Worksheet?

Eventually, I want to move the cell to the location where the last error occured. Edit: Forgot to say that I'm using Excel 2003.
As requested in comments...
Look up the 'Caller' property of the 'Application' object in the Excel VBA help. When you use it from a VBA routine, it will tell you where the call to the routine came from - what Range, Chart, etc.
An important thing to be aware of when using 'Application.Caller' is that it isn't always a Range object. Look at the help, but the property returns a Variant value that can be a Range, String, or Error. (It is a Range object in the case you're interested in, but you'll need to be aware of this.)
Because of the above, and the vagaries of VBA syntax when it comes to objects vs. values, it can be tricky to use 'Application.Caller'. Putting a line like:
Debug.Print Application.Caller.Address
in your code will fail when the caller isn't a Range. Doing something like:
Dim v
v = Application.Caller
will "compile", but will create circular references when the caller is a Range because you're trying to access the value of the calling Range.
This all means that it's probably best to write a little utility function for yourself:
Public Function currentCaller() As String
If TypeOf Application.Caller Is Range Then
Dim rng As Range
Set rng = Application.Caller
currentCaller = rng.Address(External:=True)
Else
currentCaller = CStr(Application.Caller)
End If
End Function
and then call it from your error handlers where you want to know where the call came from.
One more thing - obviously this can only tell you the caller once a VBA routine has actually been called. If you have errors in your calling formulas, Excel will return error values to your cells without ever calling your VBA routines.
Wrap your VBA function in another function that stores the cell location and value as variants. Keep this 'wrapper' function as basic as possible so it won't cause any additional errors.
If you're trying to debug app-crashing errors, the wrapper function could even store those values in a comma-delimited text file. Once stored, Excel can crash all it wants and you'll still know what the cell location and value were since you stored them outside of Excel beforehand.
Could this be done with an error handler?
An example of what I mean below:
sub code1()
on error goto cell A1
end sub