VBA, Excel, use Range to fill 2D-Array [duplicate] - vba

This question already has answers here:
Why am I having issues assigning a Range to an Array of Variants
(3 answers)
Closed 7 years ago.
I do not understand this behaviour:
Sub tuEs()
Dim A() As Variant
A = Range("A1:A10") ' works
Dim B() As Variant
B = ActiveSheet.Range("A1:A10") ' Type mismatch
End Sub
The first version works the 2nd version does not. Why? What is the difference?

The way to go with this is by adding the ".value" at the end of the range. This is usually a good idea to make things very explicit (the reason you can omit this is because value is the default property for the range object)
I added all the values to watches to see what was going on and apparently there is a problem of Excel not been able to effectively ( and implicitly ) cast the object on the fly. Note in the picture how the expression that is failing "ActiveSheet.Range("A1:A10") is of type: Variant/Object/Range; the transition from Variant to object is most likely causing the issue.
A way to force it to cast correctly would be to split the process in two parts the first one casts to range and the second one casts to a variant array. Look at my example
Also notice that if you declare the variable as variant alone and not an array of variants (dim E and not dim E()) it will get it because the it will adapt to what is needed.
Sub tuEs()
'Works
Dim A() As Variant
A = Range("A1:A10")
' Type missmatch
Dim B() As Variant
B = ActiveSheet.Range("A1:A10")
' Fix to make it cast properly
Dim C() As Variant
Dim r As Range
Set r = ActiveSheet.Range("A1:A10")
C = r
' Best of all options
Dim d As Variant
d = ActiveSheet.Range("A1:A10").Value
End Sub
Hope this makes is somewhat clear.

This is indeed a mystery! But this works (no need to declare an array of variant objects, just a variant). As to why it doesn't work in your code as stated, I am afraid I can't answer.
Dim B As Variant ' instead of Dim B() as Variant
B = ActiveSheet.Range("A1:A10")

At first I thought it to be a syntax issue - some hidden ambiguity that caused the interpreter to respond differently to the different statements. But to my dismay the following code works flawlessly:
Dim B() as Variant
B = Application.Range("A1:A10")
As it is syntatically identical to the crashing line in the question, the only possible conclusion, AFAIK is that the implementations of Range in the Worksheet and in the Application classes return different types of objects even if they contain, in essence, the same information. Most casts will render the same results, but for some implementation quirk, only the Application version can be cast to an array of variants. This conclusion is backed by the fact that inspecting the results of expressions Range("A1:A10") and ActiveSheet.Range("A1:A10") in debug mode result in different type informations - "Object/Range" in the first case and "Variant/Object/Range" in the second.
If this is true, the exact reason for the difference (if not an accident at all) is probably known only by the coders or other folks at MS. I am curious if this is consistent thru different versions of Office..

Related

Passing Lower and Upper Bound Array Dimensions as Arguments in VBA

I'm wondering if its possible to pass both lower and upper bound array sizes (using the "To" keyword) via arguments. Ultimately, I would like to do something like this:
sub foo
call bar(2 To 5)
end sub
sub bar(arrayDimensions)
dim myArray() as long
redim myArray(arrayDimensions)
end sub
But VBA throws a fit if I use the "To" keyword like this. Is there another easier alternative? Or am I doing something wrong? I know I could pass two arguments as a work around, but I would rather not do that if there's a better way.
Thanks in advance for any help.
Edit to clarify why I would like to do this instead of using two arguments.
I made an array class that I can use to store my arrays in and easily modify them (eg. myArray.fill(0)). But I want the user interface with this array class to be the same as just using a plan old array.
sub foo
dim regularArray() as long
redim regularArray(5)
regularArray(3) = 250
dim myArray as ArrayClass
set myArray = factory.newArrayClass(5)
myArray(3) = 250
end sub
This works great using default properties. My constructor is set up to either receive a one long argument to define the size of a 1D array. Or it can receive two long arguments to define the size of a 2D array. Or is can receive one range argument to build an array based on excel data. Or it can receive an array for its argument to instantiate with an array.
Right now my code works great as it is. But if I wanted to add a lower bound when I instantiate the class, lets say for a 2D array, then I have to add two more optional arguments, one for each dimensions.
That then leads to the question of: do two long arguments represent a 2D array, or does it represent the lower and upper bound of a 1D array. So it would get hairy in a hurry.
Passing two arguments is not a a work around, it is the way to do what you want.
Sub foo()
Call bar(2, 5)
End Sub
Sub bar(a As Long, b As Long)
Dim myArray() As Long
ReDim myArray(a To b)
End Sub
This works equally well, I guess:
Sub foo2()
Dim myArray(2 To 5) As Long
Call bar2(myArray)
End Sub
Sub bar2(myArray() As Long)
'Do stuff with myArray
End Sub
From a design point of view, it isn't clear why "foo" knows what dimensions are needed in "bar", and why "bar" can't set these dimensions (since "bar" is the function that is doing the work with "myArray" anyway). It is very unusual to see a construction like this. Also, for what its worth, I have never seen an array in practice that did not have a lower bound of either 0 or 1, so that is also a bit unusual.
You are trying to make things easier for your users which is to your credit. Unfortunately, this can make the programmers life a bit more complicated.
There are two alternative strategies.
1 use multiple constructors. Its not a sin to have multiple methods to construct your array so you might have methods such as
factory.newArrayClassBySize(5)
factory.newArrayClassOneDim(5,10)
factory.newArrayClassTwoDim(5,10,3,22)
factory.newArrayClassByArray(InputArray)
factory.newArrayClassByRange(inputXlRange)
This is, by far ,is my preferred method as it makes it very explicit what the code is doing.
An alternative strategy is to have 7 optional parameters where the name of the parameters makes it clear what the the constructor expects and will do. The limitation of this method is that users have to pay attention to the intellisense for the parameters
e.g.
Public Function newArrayClass _
( _
Optional Size as variant = Empty, _
Optional Lbound1 as variant = Empty, _
Optional Ubound1 as variant = Empty, _
Optional Lbound2 as variant = Empty, _
Optional Ubound2 as Variant = Empty, _
Optional XlRange as variant = Empty, _
Optional BasedOn as variant = Empty _
) as ArrayClass
Using the named parameters requires you todo more work to work out if a correct set of parameters has been provided.

VBA variable declaration okay on two lines but not when comma separated. Compiler bug?

I have the following variable declaration in a procedure which works fine.
Dim table_rng As Range
Dim attachments_rng As Range
I did have it as:
Dim table_rng, attachments_rng As Range
but this caused a "Compile error: ByRef argument type mismatch".
Since I have working code I'm not in a crisis but I wasted an hour finding this solution.
I'm using Excel 2010 with Visual Basic for Applications 7.0.1628
As far as I know the second declaration is syntactically correct. Am I missing something? I searched in vain on the web and stackoverflow.com for any wisdom on the topic.
Thanks in advance.
So, please take a look at VBA editor on this. First statement seems like to declare 3 integers, but not. Variable i and j are variant and only k is integer.
So if you look on your code, table_rng is declared as variant/empty and maybe this is what causing issue, but without rest of your code i cant tell you more.
But i recommend you to use single variable declaration on line, its far more easier to read. Or if you wana to declare more variable on single line, you need to specifi date type for each of them (like last line on my example. If you wana to know variable types, use debugging in VBA and View/Locals window).
Sub testVarDeclar()
Dim i, j, k As Integer
Dim a As Integer, b As Integer
Dim table_rng, attachments_rng As Range
Dim table_rng2 As Range, attachments_rng2 As Range
End Sub

What is the difference between `Array` and `Array()`?

I could declare an array thus:
Dim arrTest() As Variant
or thus:
Dim arrTest2 As Variant
arrTest2 = Array()
however, the first can only be passed as an argument like this:
Sub(ByRef arrTest() As Variant)
and the second like this:
Sub(ByRef arrTest2 As Variant)
you can do this with the second:
ReDim arrTest2(UBound(arrTest2) + N)
but not with the first.
What is the difference between a variable array declared the first way, and a variable array declared the second?
They're both the same vartype() - 8204 - Array of variants
Why do macros treat them differently?
The first is an array of variants. It is always an array -- it can't be reassigned to be e.g. a Range. The second is a variant which can hold just about anything, including an array (which it does in this case). Consider the following code:
Sub test()
Dim arrTest() As Variant
Dim arrTest2 As Variant
arrTest2 = Array()
Debug.Print "arrTest is a " & TypeName(arrTest)
Debug.Print "arrTest2 is a " & TypeName(arrTest2)
End Sub
When you run it, you get this:
arrTest is a Variant()
arrTest2 is a Variant()
Which is what strikes you as strange. If they are the same type why does VBA sometimes treat them as different?
Answer -- they aren't the same type! typename (or just varType), when applied to a variant variable doesn't return the variables type at all. Instead it returns the subtype of the variable (a concept that only makes sense for variants). To get a clearer picture of what is happening -- put a break point before the first Debug.Print statement, run it, and open up the Locals Window:
Note how the type of arrTest is Variant() but arrTest2 is Variant/Variant(0 to -1). They really aren't the same type. arrTest is an array so it has to follow VBA syntax regarding array. arrTest2 isn't an array at all -- it is simple variable (of type variant) so it follows the VBA syntax of simple (non-array) variables. The fact that in this particular case it is pointing to an array doesn't make it an array variable.
If you are familiar with C, your question is similar to the question that many beginning C programmers ask about the difference between int and int*, only in some ways it is more mysterious here since the fact that functions like varType and TypeName implicitly dereference any variant variable is arguably a design flaw. In some way it would be nice if typename(arrTest2) would return the more accurate (albeit more verbose) Variant/Variant() in this case.

Declaring and assigning a value to a variable

This morning I decided to open my book of stupid questions and found one question I can't get out of my head (might be that the question should be on code review, but you tell me).
So here it goes - now in VBA you normally would declare a variable and assign a value to it by what I would use as standard like this:
Dim n as Integer
n = 1
Or with objects using Set:
Dim wb as Worksheet
Set wb = ActiveSheet
But there is also an other syntax possibility here that allows you to both declare and assign a value at the same line by doing this (lets call this alternative way):
Dim n as Integer: n = 1
Dim wb as Worksheet: Set wb = ActiveSheet
Now we have two ways to declare and assign a variable. What my book of stupid doesn't tell is is there some reason or case where the alternative way will not work or why it's almost never used? To my head if variable is at the beginning of a program given a value, it would be easier to read the code syntax using alternative syntax.
O please wise and mighty SO members, enlighten me please - when would I use or should I use the alternative way at all?
There's actually no difference; the ":" is just a line separator, formatting if you will - not a convention specific to variable declaration:
http://msdn.microsoft.com/en-gb/library/ba9sxbw4.aspx
In some cases, using it makes the code more readable, but clearly it also has the potential to confuse :)

Function calls another function but gets "stuck"

I'm an occasional VBA programmer, for fun (not my job).
I have a series of VBA modules in MS Excel 2010. Can't figure out what I did wrong. This routine worked, then I changed some things and it stopped working. One of the things I did was split the code from a single function to two functions in two modules. I think it worked for a while after I split it into two, but now I can't remember if that is true since I've tried so many things to make it work again. Luckily, I saved the older version with all of the code in a single function, and it still works. It returns a large array to the spreadsheet.
Fundamentally, I have a worksheet that calls a function. That function calls another function. Using the Debug - Toggle Breakpoints in combination with some MsgBox calls, I have figured out that the first function runs down to the point that it calls the second function. Then the second function runs down to the "End Function" command. At that point, the name at the top of the worksheet flickers a few times...and nothing. While debugging, the program does not seem to return to the first function. The array that is supposed to be populated in the first function is filled with #Value.
I read several places where VBA can be corrupted, so shutting everything down and rebooting can fix it. It didn't. Then I read that if you copy/paste everything into a new worksheet, with new modules (yes, a LOT of copy/pasting), that can clean it up. It didn't.
I also read of an issue where dimensioning arrays had problems when the dimensions were input variables to the function. So I initialized the variables used to set the array dimensions, even though they were input variables to the function. Maybe that's a problem?
The code is really long, so I've only included the call to the second function, the declarations of variables in the second function, and a few other things. Maybe I screwed up some syntax when I passed variables?
The first function:
Option Explicit 'forces all variables to be explicitly declared
Function InputOutputDVL(InData As Variant)
'---------------------------------------------------------------------------------------------
Dim g, p, ng, np, ns, ID, count As Integer
Dim ngmax, npmax, nsmax, namax, nxmax, nymax As Integer
Dim row As Integer
Dim panelmax As Integer
Dim TLstyle As Integer
Dim Group(), Part(), Section(), Airfoil() As Variant
Dim ABP(), TV() As Double
ngmax = 20
npmax = 100
nsmax = 1000
namax = 10
ReDim Group(1 To ngmax, 1 To 4)
ReDim Part(1 To npmax, 1 To 6)
ReDim Section(1 To nsmax, 1 To 17)
ReDim Airfoil(0 To 100, 0 To 2 * namax + 1)
'missing code here
MsgBox ng & " " & np 'This msgbox works correctly and give the right value for np
ABP = Section2VL(nxmax, nymax, ns, panelmax, Group, Part, Section, Airfoil)
MsgBox "Completed Section2VL" 'The code never gets to this msgbox
InputOutputDVL = ABP 'I've tried setting this to = 1234 assuming there was a problem with
'ABP, but the cells on the spreadsheet are still #Value
End Function
The second function:
Option Explicit 'forces all variables to be explicitly declared
Function Section2VL(nxmax, nymax, ns, panelmax, Group, Part, Section, Airfoil)
Dim i, j, k, l, c1, c2 As Integer
Dim g, p, s, ng, np, chord, span, panel, ID, count As Integer
Dim NX, NY As Integer
Dim station, panelID, panelIDref As Integer
Dim pi, Xstyle, Ystyle As Double
Dim angle, dist As Double
Dim sx(), sy() As Double
Dim Scoord(), ABP() As Double
ns = 6
nxmax = 12
nymax = 12
panelmax = 300
ReDim sx(0 To nxmax), sy(0 To nymax)
ReDim Scoord(1 To ns, 0 To nxmax, 1 To 3), ABP(1 To panelmax, 1 To 32)
MsgBox ABP(panelmax, 5) 'This call works, and provides the proper value in the msgbox
'return ABP vector
Section2VL = ABP
'I've also tried just returning an integer thinking there was something wrong with the
'ABP array, like 987, but that doesn't work either
End Function 'This is where it stops when debugging. Doesn't return to first function
Thanks in advance. I've blown two long evenings and can't figure it out.
You issue is incompatable types between InputOutputDVL.ABP, Section2VL.ABP and Section2VL itself
Try declaring all thses () As Double
ie.
In InputOutputDVL
Dim ABP() As Double
In Section2VL
Dim ABP() As Double
And
Function Section2VL(...) As Double()
Note on Type declarations
When you declare variables like this
Dim i, j, k, l, c1, c2 As Integer
all except the last one are infact declared as Variant's
You need to specify each variables type explicitly.
Also, don't use Integer unless you have a specific reason. Use Long instead.
Thanks Chris! You definitely led me in the right direction. I also used these two websites:
http://www.cpearson.com/excel/passingandreturningarrays.htm
http://www.cpearson.com/excel/vbaarrays.htm
Incomplete/incompatible variable declarations were the source of my problem.
First, I had forgotten that VBA requires "as Integer" or "as Double" after EACH variable, not just at the end of the line. So many of variables were VARIANTS instead of integers, doubles, etc. Thanks again Chris!
Second, and more specific, I only had to make one of Chris' changes noted above, and that was to properly declare ABP() as Double in the first calling function. ABP() was already properly called Double in the second function, and I did NOT declare the Function Section2VL as Double(). With the original code:
Dim ABP(), TV() as Double
This indicated that ABP was a VARIANT ARRAY. The later call to ABP = Section2VL() was then trying to stuff a VARIANT into a VARIANT ARRAY, and THAT was the problem. I'm still disappointed that the compiler didn't somehow say it had incompatible data types or some other error. This also explains where the problem came from. The code was previously working as two functions in two modules. As I was making some other changes, I noticed that I hadn't declared ABP as an Array, so I added the "()". I made other changes, and it stopped working. I focused on the other changes, not the seemingly minor "correction" of adding the () marks. What was really happening was that in the original code a combination of mistakes had resulted in a correctly working code! Imagine that!
So the code worked with ABP as a variant, or ABP() as a double array, but NOT with ABP() as a variant array.
Of course, the correct answer here is what Chris suggested, and that is correctly and explicitly declaring all of the variables. An interesting note is that a VARIANT (not VARIANT ARRAY) can essentially "accept" being assigned any incoming value, including a DOUBLE ARRAY.
So in my final code, I've left both Functions as VARIANTs, but I've now specifically declared them as VARIANT to make it clear. In the second function, ABP is properly declared as a dynamic DOUBLE ARRAY, and later given the proper dimensions to allocate memory space. In the first function, ABP could either be a VARIANT or a DOUBLE ARRAY, and either would work, but since I always want it to be a DOUBLE ARRAY, I've specified it as such.
Option Explicit 'forces all variables to be explicitly declared
Function InputOutputDVL(InData As Variant) as Variant
'---------------------------------------------------------------------------------------------
Dim ABP() as Double, TV() As Double 'This ABP was a variant array - which was incompatible
'code removed here
ABP = Section2VL(nxmax, nymax, ns, panelmax, Group, Part, Section, Airfoil)
InputOutputDVL = ABP
End Function
And the second, called function:
Option Explicit 'forces all variables to be explicitly declared
Function Section2VL(nxmax as integer, nymax as integer... ) as Variant
Dim Scoord(), ABP() As Double 'This was already correctly declaring ABP as Double Array, but
'now I realize Scoord was incorrectly a Variant Array, but it
'wasn't actually causing a problem with my code.
'I fixed this in my own code, but left here as example of what
'not to do!
ReDim ABP(1 To panelmax, 1 To 32)
'Code removed here
'return ABP vector
Section2VL = ABP
End Function