Excel VBA Match if value is null - vba

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

Related

Returning the address of a vlookup function in vba

I've looked through all guides I can find on this subject but not a single one solves my problem.
I want to find the cell address of this vlookup function:
vlookadd = Application.Lookup(partno, Sheet3.Range("A5:B46"), 2, False)
(Currently dimmed as variant incase that needs to be changed)
I want to find the address of the value found and make it equal a variable so something like this:
batch1add = vlookadd.Address
The value found by the lookup function works fine but the address always comes up empty after trying lots of different possible solutions
as well as using find
cells(worksheetfunction.match(partno,range("a5:a46"),0)+4,2).address
The plus 4 is due to the start row of the range being 5, for example a match in row 5 will return 1.
Per the comments, using Find and an offset should be just fine.
Function getAddress(itemToFind As Variant, lookupRange As Range, columnOffset As Integer)
getAddress = lookupRange.Find(What:=itemToFind).Offset(0, columnOffset).address
End Function
Sub Example()
Dim batch1add As String
Set batch1add = getAddress(partno, Sheet3.Range("A5:B46"), 2)
End Sub
As PEH advised in the prior answer's comments: Incorporating some error handling would be wise, as both implementations assume the value exists whenever they're called.

Find a Specific Cell entry in a Column in VBA

I'm working on creating a userform for reviewing an excel sheet. I need to search a specific column to see if the user has already reviewed the row. If they have, the cell will be filled with "Reviewed", if it hasn't been reviewed yet it will have "Not Reviewed" in it.
Each department has their own Column logging whether the Row has been reviewed or not. I.e. Dept1 may have reviewed the row, while Dept2 has not yet.
I've tried something like
With Sheets("ECR")
UnReviewedRow = .Range(DepartmentReviewColumn:DepartmentReviewColumn).Find(what:="Not Reviewed", after:=.Cells(DepartmentReviewColumn, 3)).Row
End With
But I'm getting an error, not quite sure where it's coming from though. The hardcoded "3" is because I know that all entries begin at row three, everything above is headers.
I've found a few different similar questions, but they all assume that the column being searched will be the same every time. My issue is that I don't want to code this for each department, I'd like to be a bit more elegant than that.
Something like this:
With Sheets("ECR")
UnReviewedRow = .Range(DepartmentReviewColumn & ":" & DepartmentReviewColumn).Find(What:="Not Reviewed", After:=.Cells(3, DepartmentReviewColumn)).Row
End With
Modifications made:
Range() now takes a properly built string argument;
Cells() now has a row number as its first argument, and a column expressed as a string as its second argument.
Note that this code will fail with Object variable or With block variable not set if the search string is not found. You can handle this situation by initializing UnReviewedRow to e.g. 0 (zero) and putting On Error Resume Next above the Find call, and On Error Resume <either 0 or your original error handler's label> below the Find call. Then, check if UnReviewedRow = 0 and act appropriately.
Always put Option Explicit at the top of modules and classes, and compile your code (Debug / Compile VBAProject). When posting, include the text of all errors encountered.

What are the benefits and risks of using the StrPtr function in VBA?

While looking for a way to test when a user cancels an InputBox, I stumbled across the StrPtr function. I believe it checks if a variable was ever assigned a value and returns zero if it was never assigned and some cryptic number if it was.
It seems like a useful function! I started with this code:
Dim myVar as string
myVar = InputBox("Enter something.")
MsgBox StrPtr(myVar)
The message box shows a zero if the user cancelled.
Fantastic! But then why do some insist that StrPtr never be used? I read it's unsupported. Why does that matter?
A good answer will explain benefits (beyond my example above) and risks of using the StrPtr function, possibly how you use (or don't use) it without giving an opinion as to whether everyone or no one should use it.
tldr; There's no real risk to using StrPtr like that, but there's not really a benefit either.
While it might look like you get a null pointer back from the InputBox call, you actually don't. Compare the result of StrPtr to VarPtr:
Sub Test()
Dim result As String
result = InputBox("Enter something.") 'Hit cancel
Debug.Print StrPtr(result) '0
Debug.Print VarPtr(result) 'Not 0.
End Sub
That's because InputBox is returning a Variant with a sub-type of VT_BSTR. This code demonstrates (note that I've declared result as a Variant so it doesn't get implicitly cast - more on this below):
Sub OtherTest()
Dim result As Variant
result = InputBox("Enter something.") 'Hit cancel
Debug.Print StrPtr(result) '0
Debug.Print VarPtr(result) 'Not 0.
Debug.Print VarType(result) '8 (VT_BSTR)
Debug.Print TypeName(result) 'String
End Sub
The reason why StrPtr returns 0 is because the return value of InputBox is actually malformed (I consider this a bug in the implementation). A BSTR is an automation type that prefixes the actual character array with the length of the string. This avoids one problem that a C-style null terminated string presents automation - you either have to pass the length of the string as a separate parameter or the caller won't know how large to size a buffer to receive it. The problem with the return value of InputBox is that the Variant that it's wrapped in contains a null pointer in the data area. Normally, this would contain the string pointer - the caller would dereference the pointer in the data area, get the size, create a buffer for it, and then read the N bytes following the length header. By passing a null pointer in the data area, InputBox relies on the calling code to check that the data type (VT_BSTR) actually matches what is in the data area (VT_EMPTY or VT_NULL).
Checking the result as a StrPtr is actually relying on that quirk of the function. When it's called on a Variant, it returns the pointer to the underlying string stored in the data area, and it offsets itself by the length prefix to make it compatible with library functions that require a C-string. That means the StrPtr has to perform a null pointer check on the data area, because it's not returning a pointer to the start of the actual data. Also, like any other VARTYPE that stores a pointer in the data area, it has to dereference twice. The reason VarPtr actually gives you a memory address is that it gives you the raw pointer to whatever variable you pass it (with the exception of arrays, but that's not really in scope here).
So... it's really no different than using Len. Len just returns the value in the header of the BSTR (no, it doesn't count characters at all), and it also needs a null test for the similar reason that StrPtr does. It makes the logical conclusion that a null pointer has zero length - this is because vbNullstring is a null pointer:
Debug.Print StrPtr(vbNullString) '<-- 0
That said, you're relying on buggy behavior in InputBox. If Microsoft were to fix the implementation (they won't), it would break your code (which is why they won't). But in general, it's a better idea to not rely on dodgy behavior like that. Unless you're looking to treat the user hitting "Cancel" differently than the user not typing anything and hitting "Enter", there really isn't much point in using StrPtr(result) = 0 in favor of the much clearer Len(result) = 0 or result = vbNullString. I'd assert that if you need to make that distinction, you should throw together your own UserForm and explicitly handle cancellation and data validation in your own dialog.
I find the accepted answer to be rather misleading, so I was compelled to post another one.
A good answer will explain benefits (beyond my example above) and risks of using the StrPtr function, possibly how you use (or don't use) it without giving an opinion as to whether everyone or no one should use it.
There are three "hidden" functions: VarPtr, StrPtr and ObjPtr.
VarPtr is used when you need to get the address of a variable (that is, the pointer to the variable).
StrPtr is used when you need to get the address of the text data of a string (that is, the BSTR, a pointer to the first Unicode character of the string).
ObjPtr is used when you need to get the address of an object (that is, the pointer to the object).
They are hidden because it may be unsafe to mess around with pointers.
But you cannot go completely without them.
So, when do you use them?
You use them when you need to do what they do.
You use VarPtr when your problem in hand is "I need to know the address of that variable" (e.g. because you want to pass that address to CopyMemory).
You use StrPtr when your problem in hand is "I need to know the address of the first character of my BSTR string" (e.g. because you want to pass it to an API function that accepts wide strings only, but if you simply declare the parameter As String, VB will convert the string into ANSI for you, so you have to pass StrPtr).
You use ObjPtrwhen your problem in hand is "I need to know the address of that object" (e.g. because you want to examine its vtable or manually check if the object address does or does not equal some value you knew previously).
These functions correctly do what they are supposed to do, and you should not be afraid to use them for their intended purpose.
If your task in hand is different, you probably should not be using them, but not out of fear that they will return a wrong value - they will not.
In a perfect world, you would stop at that conclusion. That is not always possible, unfortunately, and the InputBox situation you mention is one of the examples.
From what is outlined above, it would appear that you should not be using StrPtr to determine if Cancel was pressed in an InputBox. Realistically though, you don't have a choice.
VBA.InputBox returns a String. (This fact is incorrectly omitted from the current documentation making it look like it returns a Variant.) It is perfectly okay to pass a string to StrPtr.
However, it is not documented that InputBox returns a null pointer on a cancel. It is merely an observation. Even though realistically that behaviour will never change, theoretically it may in a future version of Office. But that observation is all you have; there is no documented return value for a cancel.
With this in mind, you make a decision on whether or not you are comfortable with using StrPtr on the InputBox result. If you are happy to take the very small risk of this behaviour changing in future and your app therefore breaking, you do use StrPtr, otherwise you switch to Application.InputBox that returns a Variant and is documented to return a False on a cancel.
But that decision will not be based on whether StrPtr is correct in what it tells you. It is. It is always safe to pass the String result of VBA.InputBox to it.
Fantastic! But then why do some insist that StrPtr never be used? I read it's unsupported. Why does that matter?
When someone insists that something should never be used, it's almost always wrong. Even GoTo has its correct uses.
I tired both using StrPtr and without using StrPtr. I tested my Sub with several examples. I got same results except in one occasion - When User inputs null value (nothing) and presses OK.
Precisely I tried these two:
Using StrPtr. "Invalid Number" was the result here
ElseIf StrPtr(Max_hours_string) = 0
MsgBox "Cancelled"
Else
MsgBox "Invalid Number"
Without Using StrPtr. "Cancelled" was the result here
ElseIf Max_hours_string = "" Then
MsgBox "Cancelled"
Else
MsgBox "Invalid Number"
This is my code.
Sub Input_Max_Hours_From_The_User()
'Two Common Error Cases are Covered:
'1. When using InputBox, you of course have no control over whether the user enters valid input.
' You should store user input into a string, and then make sure you have the right value.
'2. If the user clicks Cancel in the inputbox, the empty string is returned.
'Since the empty string can't be implicitly coerced to a double, an error is generated.
'It is the same thing that would happen if the user entered "Haughey" in the InputBox.
Dim Max_hours_string As String, Max_hours_double As Double
Max_hours_string = InputBox("Enter Maximum hours of any Shift")
If IsNumeric(Max_hours_string) Then
Max_hours_double = CDbl(Max_hours_string) 'CDbl converts an expression to double
Range("L6").Value = Max_hours_double
Range("L6").Interior.ColorIndex = 37
ElseIf StrPtr(Max_hours_string) = 0 Then 'ElseIf Max_hours_string = "" Then MsgBox "Cancelled" also works !
MsgBox "Cancelled"
Else
MsgBox "Invalid Number"
End If
End Sub
So I think it depends how important it is to handle the null value for you. All other test cases, including pressing Cancel, non-numerical inputs etc. give the same results. Hope this helps.
Read through this thread and ultimately ended up doing the following... which does exactly what I want.... If the user deletes the previous entry which is the default... and clicks ok.. it moves forward and deletes the back end data ( not shown ). If the user click's cancel, it exists the sub without doing anything. This is the ultimate objective and... this allows it to work as intended... Move forward unless cancel is clicked.
hth,
..bob
Dim str As String
If IsNull(Me.Note) = False Then
str = Me.Note
Else
str = "Enter Note Here"
End If
Dim cd As Integer
cd = Me.ContractDetailsID
str = InputBox("Please Enter Note", "NOTE", str)
If StrPtr(str) = 0 Then
Exit Sub 'user hit cancel
End If
In my opinion: Using StrPtr in order to identify if a value converts to 0 is extra code to write. if you use the following function like your example above
Sub woohoo()
Dim myVar As String
myVar = "hello"
myVar = InputBox("Enter something.")
'if Cancel is hit myVar will = "" instead of hello.
'MsgBox StrPtr(myVar) not needed
MsgBox myVar 'will show ""
End Sub
Now is this the only reason to not use StrPtr no not at all. The other issue you run into with using unsupported functions is that eventually they can break the application. Whether its a library issue or another programmer looking through your code and trying to find that function it just is not a good idea. This may not seem like a big deal if your script is only 100 lines long. But what about when it is thousands of lines long. If you have to look at this code 2 years down the road because something broke it would not be very fun to have to find this magical function that just does not work anymore and try to figure out what it did. Lastly especially in VBA you can get overflow errors. If StrPtr is used and it goes past the allocated space of your data type that you declared it's another unnecessary error.
Just my 2 cents but due to being able to use less code and the function being more stable without it I would not use it.
10+ years Excel Programmer.

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.