VBA Range not being assigned to an Array - vba

So rather simply I defined a test function:
Option Base 1
Function TestFunction(InputRange As Range)
Dim TestArray() As Variant
TestArray = InputRange
Debug.Print TestArray(5)
End Function
I then call it on a sheet with =TestFunction(A:A), and expect to see the value in row 5 printed to the Immediate window. Instead I don't get any output. The first 100 or so rows of A have data so I'd expect TestArray(5) to output something.
I've also tried changing the above to:
TestArray = InputRange.Value
and:
TestArray = Range(InputRange)
None of this seems to work.

The issue is when assigning a range to an array it is made into a two dimensional array regardless if it is one column or one row. So all references must refer to it as a two dimensional array:
Debug.Print TestArray(5,1)

Related

Declaring Array() in VBA-ACCESS does not work without upper limit

I am learning about declaring arrays. It works when I declare it by giving an upper limit using following code:
Dim arrayA(5) as String
I check it by assigning a random value:
arrayA(0) = 1
MsgBox arrayA(0)
MsgBox responds by giving a value of 1.
However, my actual intention is to create a dynamic array that I defined as below:
Dim arrayA() as String
I test it in the same way
arrayA(0) = 1
MsgBox arrayA(0)
But this time it does not work and MsgBox pops up empty. Would someone tell me if I need to load some libraries to work with dynamic array?
Arrays in VBA need to be initialized before they are used.
You can initialize an array with a Redim statement:
Dim arrayA() as String
Dim i As Integer
i = 0
Redim ArrayA (0 To i)
arrayA(0) = "1" 'String
MsgBox arrayA(0)
Alternatively, there are functions that return an initialized array. In that case, Redim is not needed as the initialization happens in the external function. You do need to make sure you match type with the array being returned, though, and the overhead is the same or more.
Dim arrayA() as Variant
arrayA = Array(1)
MsgBox arrayA(0)
You can declare an array without a limit, but must redim the array to the desired limit prior to using it:
Dim myArray() As Long
Redim myArray(0)
myArray(0) = 0 'etc...
So, you cannot use a "dynamic" array in VBA like this.
The best reference I've ever known for arrays (and much other VB/A related information), comes from the late Chip Pearson: http://www.cpearson.com/Excel/VBAArrays.htm

Scripting.Dictionary adding items without using Dictionary.Add - BUG?

Scripting.Dictionary likes to add values for no reason, when you look a value up! Demonstrated with a 30 second example:
Create a new worksheet, and fill in A1:A4 = {1,2,3,4}
Insert a new vba module and add this code
Public Sub test()
Dim rowIndex As Integer
'
Dim dict As Scripting.Dictionary
Set dict = New Scripting.Dictionary
For rowIndex = 1 To 4
dict.Add Trim(Sheet1.Cells(rowIndex, 1).Value), rowIndex
Dim notEvenAddingSoWhyAreYouAdding As Variant
notEvenAddingSoWhyAreYouAdding = dict(Sheet1.Cells(rowIndex, 1).Value)
Next rowIndex
End Sub
Put a breakpoint on Next rowIndex
Run the sub and inspect the value of dict. It now has two values, "1" and 1, as you can see in the image below:
What. The. Hell?!
I realise I have the Trim(...) function in the dict.Add() line, which means there are two distinct keys in use, but why is it adding an extra value when it does a lookup?! That makes no sense whatsoever - now dict.Count would not give the value I would expect.
As you pointed out, you have two different keys, 1 and "1". If you try to access a key that doesn't already exist in the dictionary, it will add it automatically. It is strange behaviour.
I'd use something like this:
If dict.Exists(something) Then
myVariable = dict.Item(something)
End If
You creating one key as a string representing 1 (e.g. "1") and the key's item as the number 1 using the conventional dict.Add <key>,<item>.
Immediately afterwards, you shortcut add another with the number 1 as the key.personally, I shortcut the add/overwrite with dict.item(1) = "abc" but your method works as well.
tbh, I'm not even sure if .CompareMode = vbTextCompare would resolve a strin 1 to equal a numeric 1. In any event, you are currently on a vbBinaryCompare so there is no way it will match.

Creating a new String Array from another

I have an array of integers (dfArray) and would like to create a new second String array which consists of the integers and appending "G" to the beginning. What's the best way to go about this? I was thinking of For Each but couldn't get it to work. Thanks in advance.
Set dfArray = [dff]
Set dfArray2 = ["G" & dff] 'incorrect but you get the idea?
Dim dfArray() As Variant
Dim dfArray2() As String
dfArray = [dff].Value
ReDim dfArray2(UBound(dfArray)) As String
Dim i As Double
For i = 1 To UBound(dfArray) Step 1
dfArray2(i) = "G" & dfArray(i, 1)
Next i
Anyways, from my personal point of view, I don't like to asign a complete Range into Array, only if needed. I prefer to loop using Lbound or Ubound and control the array all the time. To asign a range into an Array, you need the Array variable to be Variant type, and also, you can't use Preserve easily. Check this question for more info
Issues about Variant Arrays

VBA Excel: How to pass one parameter of different types to a function (or cast Int/String to Range)?

I'm writing some VBA functions in Excel that compute word values and cross sums of the input.
I'm passing the input as Public Function cross_sum(myRange As Range) As Integer to them so that they take cell references as input, e.g. =cross_sum(A1). Works fine.
However when I try to chain two functions like =cross_sum(word_value(A1)) I run into th VALUE error because word_value() returns an Integer value and not the Range cross_sum() is set to expect. However I did not find a way to cast an Integer (or String) into a Range.
As Excel's built-in functions support chaining as well as Range input I wonder how.
Unfortunately this is my first VBA project so I wonder if and how to cast or what type to choose to get this working both ways.
Any pointers appreciated!
TIA,
JBQ
You can pass Variant to a function and the function can determine the type of input:
Public Function Inputs(v As Variant) As String
If TypeName(v) = "Range" Then
MsgBox "you gave me a range"
Else
MsgBox "you gave me a string"
End If
Inputs = "done"
End Function
Sub MAIN()
Dim st As String
Dim rng As Range
st = "A1"
Set rng = Range(st)
x = Inputs(st)
x = Inputs(rng)
End Sub
Without your code, it is hard to know what you could change. That being said...
There is not a way to convert an integer to a range. You would have to create a function to do so if that is what you desired.
You could create a converter function, maybe titled IntegerToRange, that takes an integer and after some logic (maybe 1 = "A1", 2 = "A2" or something), will return a range. Your cell formula would then be =cross_sum(IntegerToRange(word_value(A1))
Alternatively, you could modify your word_value function to return a range instead of an integer. Your cell formula would then be =cross_sum(word_value(A1).

When one should use Set [e.g for SpecialCells return value]?

I am afraid I misunderstand the documentation of VBA for excel, I have this line which seems to be an error:
Range a = Selection.SpecialCells(xlCellTypeConstants, 23)
But this one is just fine:
Set a = Selection.SpecialCells(xlCellTypeConstants, 23)
The documentation claims:
Returns a Range object that represents all the cells that match the specified type and value.
But it actually returns a byRef object and that is why I have to use Set.
What do I miss here?
Here is Range.SpecialCells method help in Excel:
Range a = Selection.SpecialCells(xlCellTypeConstants, 23)
This is not valid VBA, regardless of data type. You don't declare variable type in front of variable name, as you would do in C#, and you don't initialize variable at the point of declaration, as you would do in VB.NET.
You can do:
Dim a As Range
Set a = Selection.SpecialCells(xlCellTypeConstants, 23)
This will save a reference to the range into a.
You can also do:
Dim a As Variant
a = Selection.SpecialCells(xlCellTypeConstants, 23)
This will save in a a 2D array of values of cells in the range.
Returns a Range object that represents all the cells that match the specified type and value.
But it actually returns a byRef object and that is why I have to use Set.
There are no byval objects in VBA. All objects are byref, and when you want to copy a reference to an object, you always use Set. The reason why you need Set is default properties. Each object can have a default property that is requested when only object name is provided. This creates ambiguity, so you need to say Set when you need to manipulate the object reference itselt, and omit Set when you want the default property of an object. The default property of Range is Value.
Object variables are assigned using the Set keyword. Non-object variables (let's ignore variants for now) do not use the Set keyword
dim a as int
dim b as string
dim c as boolean
a = 1
b = "hello"
c = false
dim a as Range
dim b as Worksheet
dim c as PivotTable
set a = ActiveSheet.Range("a1")
set b = ActiveSheet
set c = ActiveSheet.PivotTables(1)
Range.SpecialCells Method returns a Range object that represents all the cells that match the specified type and value.
set keyworkd is used to assign a reference to an object.
There are two flavors of assignments in VBA: one for ordinary variables, which use Let, and one for object variables, which use Set.
An ordinary (string, logical, numeric) variable is one that points to the location in memory where the variable is stored.
An object variable (all the things you find in the language reference under Objects) is one that points to a structure in memory (a VTable) that in turn contains pointers to the object's properties and methods.
Reference http://www.excelforum.com/excel-programming-vba-macros/693357-when-to-use-the-keyword-set.html
Do not know it this matters, but Value is not default property of Range. Default property of Range is _Default.
It is defined like this
_Default([in, optional] VARIANT RowIndex, [in, optional] VARIANT ColumnIndex) and it represents the array of values of particular range.
Sub test()
Dim rng As Range
Set rng = ActiveSheet.Range("A1:C3")
' all cells in range rng become value of 1
rng.Value = 1
' all cells in range rng become now value of 2
rng.[_Default] = 2
' first cell in range rng become value of 3
rng.[_Default](1, 1) = 3
' nothing changes
rng.Value()(1, 1) = 4
Dim a1, a2
' however both Value and _Default return same array of variants
a1 = rng.Value
a2 = rng.[_Default]
End Sub