Sort Range and Store Result in VBA Excel - vba

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.

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")

Excel VBA - Storing table column into a range variable

I am currently experimenting with excel VBAs ListObjects which is the object type of an excel table. I would like to store a table column range into a variable.
Here is what I can do:
'store a group of cells into a range variable
dim rng as Range
set rng = activesheet.Range("A1:A10")
'select a table column
dim table as ListObject
set table = activesheet.listobjects("Table1")
table.ListColumns(1).Range.Select
While both of the above work, I don't understand why the following does't work: (EDIT: it works)
dim rng_column as Range
set rng_column = table.ListColumns(1).Range
I experimented with other variable types, such as variant or ListColumn, however, nothing stored the cells into the variable. In the example above, there is no error, but the variable rng_column remains <empty>
I know there are other workarounds, but I really want to understand what's the issue here.
UPDATE
After some helpful comments I could narrow the problem down. Everything worked normally. However, I made two mistakes.
First, I had a spelling error in the variable names. (I should have included option explicit to notice this earlier).
Second, I passed the range variable on to another range variable, where I forgot to include the set keyword when doing so..
Hopefully someone can learn from my mistakes..
you have a misnomer in the title. technically you cannot store any data into a range variable.
a range variable is a reference (a pointer) to a cell, or a group of cells. the data gets stored in the cells that the range variable points to.
if you need to store data from a range, then assign the range.value to an array
if you want to store data from a single cell then assign the value to a "regular" (non object) variable (string, integer, long ... )

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.

troubles passing a discontinuous named range into a custom function

I've been looking around stack overflow for an answer to this for longer than I care to admit now.
Here's what I have: In a worksheet I have a bunch of discontinuous cells which I need to check for the existence of specific text. I've created a simple function to do this and can do this easily when I define that range manually (in code).
However, when I procedurally create a named range (while doing other stuff) and then try passing in the named range, the function never executes.
I know that the named range is being properly created because I have auto-formatting on it and also I can reference the range with excel formula which accept discontinuous ranges (SUM and whatnot).
Here's the pertinent portions of my code:
Function customProcess1(NamedRange As Range) As Long
For Each c in NamedRange.Cells
...
Next c
End Function
In Excel when I type the formula as "=customProcess1(A1:A2)" I get my number back after the function runs. When I type in "=customProcess1(NamedRange)" my function never even executes.
Again, I'm using the named range as defined already in the document. I can observe the name in the name manager, it references the appropriate cells, i can use the range in formula which accept non-continuous ranges, etc. I can't figure out how to get my working named range into my function.
When I put the formula as "=customProcess1("NamedRange")" the function executes, but since the named range is not ""NamedRange"" but is "NamedRange" it fails to set the object as Range (the object is not found). I've tried taking the named range as a string, but again, if I don't put the quotes around the name, it won't even run the function. So then I've tried passing in a string with the quotes and taking the quotes off inside the function, but this isn't exactly working well either.
In short, I just want to get my non-continuous named range in my custom function. Once I do that, everything is golden.
Anyone have any ideas? I'm not sure why this has been such a chore.
I'm not sure why what you're trying doesn't work and don't really have time to research that part of it, but you could do the following:
Function customProcess1(NamedRange As String) As Long
Dim TheRange As Range
Set TheRange = Range(NamedRange)
For Each c in TheRange.Cells
...
Next c
End Function
Hope this helps.
Adapting your UDF(), I coded:
Function customProcess1(NamedRange As Range) As Long
For Each c In NamedRange.Cells
customProcess1 = customProcess1 + c.Value
Next c
End Function
I then assigned the name Mike to the cells B6,C8,D10 and placed values in these cells. I then placed the formula:
=customProcess1(Mike)
in a cell and got the following:
NOTE:
I did not use =customProcess("Mike")

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