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

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.

Related

Application.Cells VS Application.ActiveSheet.Cells

The Macro Recorder generated the following statement:
Cells.Select
Now I understand that without the object qualifier this will return all the cells as a Range object.
However, I am wondering what the fully qualified version of this statement is?
Is it:
Application.Cells.Select
Application.ActiveSheet.Cells
Application.ActiveWorkbook.ActiveSheet.Cells
In other words, which one of those fully qualified statements is actually executed by VBE when it runs Cells.Select?
What is the difference between all of these??? As all of these access the same object in the end - is it just personal preference as to which statement I would use if I wanted to explicitly qualify all the objects?
Thank you so much!
It's complicated :)
As all of these access the same object in the end
True. Keywords "in the end". The difference is how many steps it takes to get there...
Unqualified Cells (or Range, Rows, Columns, Names, etc.) aren't magic, they're member calls (Property Get) against a hidden, global-scope object cleverly named Global:
You can validate that this hidden object is involved, by blowing up in a standard module:
Sub GoesBoom()
'throws error 1004 "Method 'Range' of object '_Global' failed"
Debug.Print Range(Sheet2.Cells(1, 1), Sheet3.Cells(1, 1))
End Sub
_Global and Global are closely related - without diving deep into COM, you can consider Global the class, and _Global its interface (it's not really quite like that though - look into "COM coClasses" for more information).
But Cells is a property of the Range class:
I think it's reasonable to presume that Global calls are pretty much all redirected to Application, which exposes all members of Global, and then some.
Now as you noted, Application also have a Cells property - but Cells belong on a Worksheet, so no matter what we do, we need to end up with a Worksheet qualifier... and then any worksheet belongs in a Worksheets collection, which belongs in a Workbook object - so we can infer that an unqualified Cells call would be, in fully-explicit notation, equivalent to... (drumroll):
Application.ActiveWorkbook.ActiveSheet.Cells
But you don't need to be that explicit, because ActiveSheet has a Parent that is always going to be the ActiveWorkbook, so this is also explicit, without going overboard:
ActiveSheet.Cells
But that's all assuming global context. This answer explains everything about it - the gist of it, is that if you're in a worksheet's code-behind, then an unqualified Cells member call isn't Global.Cells, but Me.Cells.
Now, note that Cells returns a Range. Thus, whenever you invoke it against a Range without providing parameters, you're making a redundant member call:
ActiveSheet.Range("A1:B10").Cells ' parameterless Range.Cells is redundant
Let's take the post apart:
Cells.Select
Now I understand that without the object qualifier this will return
all the cells as a Range object.
That's actually somewhat incorrect. While it is true that .Cells returns a Range.Cells object which returns all the cells, Cells.Select is actually a method of the Range object which - as you may have guessed - Selects the range (in our case, all the cells)
The Select method, as per MSDN actually returns a Variant and not a Range object.
That it is a pretty important distinction to make, especially if you plan on passing that value to anything. So if we pretended to be a compiler
Cells -> ActiveWorkbook.ActiveSheet.Range.Cells returns Range of all the cells
Range.Cells.Select -> first we take our returned Range, we then select the cells in Worksheet and actually return a Variant (not Range)
As to the other part of the question. It depends where your module is placed. By default, Cells is shorthand for the following statement:
Application.ActiveWorkbook.ActiveSheet.Range.Cells
This however is subject to change depending on where your module is placed and if Application, workbook or sheet has been modified.
In general, it is a good coding practice to always specify at least a specific Worksheet object whenever you're referencing a Range, eg.
Sheets("Sheet1").Range.Cells
This is explicit and therefore less error prone and clearer to comprehend, be it for you or anyone working with your code.. You always know what exactly you're working with and not leave it to guesswork.
Obviously, the moment you start working with multiple workbooks, it's a good idea to incorporate Workbook objects statements before the Sheet. You get my point.
Last but not least, whatever you're trying to do, it's probably for the best you avoid using Select. It's generally not worth it and prone to unexpected behaviour.
Check this question here: How to avoid using Select in Excel VBA
If you just type Cells - in and of itself it does nothing. It is the same as Range.Cells. The only advantage of Cells is that it can accept numeric value for column (second argument). It's very handy when you do complex manipulations.
Range.Cells just returns Range object. When you have Range object, think of it as a small Excel worksheet. Say, you have range Range("F3:J10"). Then following ranges all refer to H3 cell:
Range("F3:J10").Cells(3)
Range("F3:J10")(3)
Range("F3:J10").Cells(1, 3)
Range("F3:J10")(1, 3)
Range("F3:J10").Cells(1, "C")
Range("F3:J10")(1, "C")
Range("F3:J10").Range("C1")

Can't get vba function to return correct answer

I'm learning VBA from a book, and the sample function in it is:
Function CubeRoot(number)
CubeRoot = number ^ (1 / 3)
End Function
If I call that function from a sub procedure, I get the correct result, but when I try to use it directly in Excel, I get a "FALSE" result in the cell. I followed the book step-by-step and it does not say to do anything that I haven't done. What am I missing/doing wrong or maybe I need to change some option? I am using Excel 2016.
Edit:
This is what I write in an excel cell: =CubeRoot(8) The result I get is FALSE. However, if I click the fX button and the function arguments box pops up, the formula result shown there is calculated correctly.
I write in an excel cell: =CubeRoot(8) The result I get is FALSE
At first try declaring the variable types properly:
Function CubeRoot(number As Variant) As Double
CubeRoot = number ^ (1 / 3)
End Function
But I do not believe that this will help because the error result then would be #VALUE! but not a boolean FALSE.
But the ^ operator can be overloaded. Maybe by some AddIn you are using. So it seems that it was overloaded to be some boolean operator.
Try the following:
Function CubeRoot(number As Variant) As Double
CubeRoot = Application.Power(number, (1 / 3))
End Function
If that helps, then my suspicion is true and you should go through your installed AddIns to determine which one is doing the weird overload.
If even the using Application.Power(number, (1 / 3)) does not help, then the cell containing the formula seems to be formatted using a weird number format. Try formatting it using the General number format.
According to your mention: "if I click the fX button and the function arguments box pops up, the formula result shown there is calculated correctly.", I suspect it is the weird number format rather than the first two suspicions. So do selecting the cell containing the formula =CubeRoot(8) and set number format General to it using Home tab and Number group.
Finally the problem was that the worksheet functions had been inputted into Excel4 macro sheets instead of worksheets. Probably the Excel4 macro sheets were added using Sheets.Add Method using the type xlExcel4MacroSheet. But in Excel4 macro sheets only Excel4 macro code is possible not default formulas as in worksheets. Excel4 macro is ancient type of macro language from Excel 4.0 (1992). Since Excel 5.0 (1993) VBA is used for Excel macros.
Declare all variables as double, and also the function return type as double.
By default 8 is treated as integer, so 8*X will result integer even if x is single or double.

What data type does find method return?

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.

Sort Range and Store Result in VBA Excel

I want to create a VBA function that accept a range, a key, and index and return a value based on it's index position.
The code look like this.
Function SortRange(datarange As Range, mycolumn As Range, position)
Dim theResult As Range
Set theResult = hasil.Sort(Key1:=kolom, order1:=xlAscending, Header:=xlNo)
SortRange= position.Cells(nomorurut, 1)
End Function
However, I always got #VALUE result. What's wrong with the code?
There appears to be a lot wrong here, or at least a lot of potentially wrong things.
First and most importantly, if you are calling this as a worksheet function, it will never work. UDFs cannot manipulate the worksheet object (the .Sort method manipulates the sheet).
Also while you indicate the function accepts a range, a key (string?) and index (integer/long?), your function is accepting two range arguments and a third un-typed variant which appears to be a worksheet Object based on the later call to position.Cells. So, the function does not appear to be accepting the arguments you expect it to accept, which may cause mismatch errors.
The following are also potential errors:
If position is not a worksheet object, you'll get an error (object does not support this property or method)
If hasil is not a global variable, or has not been instantiated, you'll get an error (object variable or with block not set)
It is not entirely clear what you are trying to do, but probably the VLOOKUP, INDEX and/or MATCH functions already will accomplish what you are attempting.

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