Passing Lower and Upper Bound Array Dimensions as Arguments in VBA - 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.

Related

Way to Have a Module Level Type be Determined Via Method, VBA

I have a class I use for Arrays in my Visual Basic Application. Its similar to array objects in other languages (myArray.length for example). It works pretty good. Except the array in the class itself is of type variant. I do this so I can instantiate using any array type I need.
But using variant type is inefficient in both memory and speed and was wondering if there was a smart way to define the type in my class so its not variant. But I can't think of a way to do this.
So right now, I have a class module type defined as:
private type Tarray
data as variant
length as long
...
end type
private this as Tarray
Then I have a factory/constructor method that does this:
public sub ArrayClass(optional rowCount as variant, optional colCount as variant)
redim this.data(rowCount,colCount)
... other stuff to handle the optional parameters
end sub
I know I could add in a data type to my parameters to define on the redim line:
redim this.data(rowCount,colCount) as long (or whatever datatype I passed in my parameters)
Although this is better than what I'm currently doing, it is still slower than just declaring it as long in the first place.
So the only thing I can think of is to have multiple array classes, one for long, one for double, etc. But this seems rather redundant and against DRY principles.
I was wondering if there is a better way to accomplish this task?
Thanks in advance.

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.

Different ways of using a variable across different subroutines

I'm trying to set up a sub to be called upon and use the value of its result in the main sub. So far I've been using Function to carry over the value. However, I was wondering if there are any alternative ways of doing the same thing? I figured ByVal/ByRef is another way to do it by using a Sub instead of Function. My current codes are as follow:
Sub Main()
Dim i as Long
i = lr("A")
'some other calculations using i
End Sub
Function lr(Tar As String) As Long
Dim twb As Workbook
Set twb = ThisWorkbook
lr = ThisWorkbook.Sheets(1).Range(Tar & Rows.Count).End(xlUp).Row
End Function
My question is, How would I write this if I were to use a Sub instead of Function? Thanks!
So far I've been using Function to carry over the value.
Great, that's what functions are for! When you only need to return a single value, the best way is always going to be a function.
Things get fuzzier when you start needing to return two or more values. You could:
Use ByRef parameters and use them as "out" values.
This is "ok" for procedures (Sub), and confusing for functions - what determines which parameter gets to be the function's return value, and which parameters get to be passed ByRef? How does the calling code know whether or not to pass an initialized value to those ByRef parameters?
A naming convention can help here:
Public Sub Foo(ByVal foo1 As String, ByRef outBar1 As String, ByRef outBar2 As String)
An "out" prefix can tell the calling code that the parameter is an out value.
Scope the variables at a level that is accessible by both the caller and the callee.
This is a bad practice that can easily lead to spaghetti code. Avoid it - variables should have the smallest necessary scope possible, and be passed between methods/functions/procedures/modules, not just globally scoped and accessed by anyone at any given time!
Create a class to encapsulate all the values the function should return.
Definitely more object-oriented, results in much cleaner, readable code. The only downside is that VBA doesn't really encourage you to do this, and consistently doing that will result in a myriad of classes that you can't quite organize.

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

Looping Through Boolean Parameters

I have a generic question to see if anyone can help me with a better solution.
I have a .NET method that takes in 20+ boolean values, passed in individually.
For each parameter that is true I need to add a value to list.
Is there a more efficient way to add the values to the list besides have an if statement for each boolean?
Example:
Public Function Example(ByVal pblnBool1 as boolean, _
ByVal pblnBool2 as boolean, _
ByVal pblnBool3 as boolean)
If pblnBool1 then
list += "A"
End If
If pblnBool2 then
list += "B"
End If
End Function
Obviously this code isn't correct but it shows what I'm trying to do.
Anyone have any ideas?
Thanks
First off, having 20+ params sucks.
Secondly, you can use the ParamArray keyword to declare that you want the values passed to you in an array. (I don't think this is CLS compliant, meaning some languages won't be able to call your function without bundling the values into an array. But VB and C# can both easily work with each other's param arrays.) If you don't want to do that, you can always create the array yourself in your function. But i'd rather let the language and/or framework do that for me.
This is not optimized or anything; it's just an example.
sub Example(paramarray bools() as Boolean)
static vals() as String = {"A", "B", "C"}
if bools.Length > vals.Length then
throw new ArgumentException(String.Format( _
"Too many params! ({0} max, {1} passed)", _
vals.Length, bools.Length _
))
end if
for i as Integer = 0 to bools.Length - 1
if bools(i) then list += vals(i)
next
end sub
I assume that list is some member variable, since it's not defined in your code. If you intend for it to be the return value, then declare it in the function, and return it at the end. (And of course, turn sub Example(...) and end sub into function Example(...) as String and end function).
If you have 32 or fewer booleans to pass, you can use an instance of BitVector32. This allows to pass them all in a single integer. It provides methods for setting and retrieving the values.
Just make an array of boolean values and do a for...each loop through it. Or if you need to be selective, a 2-D array.