Based on String return Array in Access 2010 vba - vba

I've been working on this for two days and thrashing around and here's where I am now.
Private Function SetColumns(sRptVer As String, iColumns() As Integer)
If sRptVer = "Q1" Then
iColumns(1) = 5 '<- Subscript out of range error here Hovering shows _
'"iColumns(1)=<Subscript out of range>"
iColumns(2) = 6
iColumns(3) = 7
iColumns(4) = 17
End If
If sRptVer = "Q2" Then
iColumns(1) = 5
iColumns(2) = 6
iColumns(3) = 7
iColumns(4) = 8
iColumns(5) = 9
iColumns(6) = 10
iColumns(7) = 17
End If
SetColumns = iColumns()
End Function
Private Sub Test2()
Dim iColValue() As Integer
Dim sRptVer As String
Dim iColumns() As Integer
sRptVer = "Q1"
iColValue() = SetColumns(sRptVer, iColumns())
For i = 1 To 10
Debug.Print iColValue(i)
Next i
End Sub
The goal is to be able to be able to pass a string designating the quarter and return an array that will serve to set the columns I will iterate over to get values out of an Excel spreadsheet. (I'm pulling data from Excel into Access).
I've tried starting with iColumns(0). No difference.
NOTE: Access vba does not like to set arrays like so iColumns()={1,2,3,4}. It balks at the "{}".
Access vba doesn't seem to like "ReDim" either, I get a compile error: Syntax error when I "redim icolumns() as Integer" in the SetColumns function.
Thanks in advance.

You don't need two separate arrays in your calling procedure to do what you want. Try this instead:
Private Function SetColumns(sRptVer As String) As Integer()
Dim iColumns() As Integer
Select Case sRptVer
Case "Q1"
ReDim iColumns(1 To 4)
iColumns(1) = 5
iColumns(2) = 6
iColumns(3) = 7
iColumns(4) = 17
Case "Q2"
ReDim iColumns(1 To 7)
iColumns(1) = 5
iColumns(2) = 6
iColumns(3) = 7
iColumns(4) = 8
iColumns(5) = 9
iColumns(6) = 10
iColumns(7) = 17
End Select
SetColumns = iColumns
End Function
Private Sub Test2()
Dim sRptVer As String
Dim iColValue() As Integer
sRptVer = "Q1"
iColValue() = SetColumns(sRptVer)
Dim i As Integer
For i = LBound(iColValue) To UBound(iColValue)
Debug.Print iColValue(i)
Next i
End Sub
I made a few other changes:
I used ReDim to dynamically size the array based on the parameter passed to SetColumns.
I passed both lower and upper bounds to the array in the ReDim statement. Arrays in VBA are zero-bound by default*, so ReDim iColumns(4) is actually equivalent to ReDim iColumns(0 To 4).
Because we no longer know the size of the array ahead of time, I use the LBound() and UBound() functions to walk the array.
I changed your two If statements to a Select...Case statement. Based on your limited sample code that you provided this seemed appropriate. If your actual code is more complex, it may not be.
*NOTE: Unfortunately, you can change the starting array base in VBA using the Option Base statement. Please don't do this. Ever. You will only confuse yourself and others. If you want a specific array to start from 1 then be explicit when you Dim or ReDim the array as I showed in my example.

Related

VBA UBound Function

I'm trying to explore UBound applications for my code in Visual Basic for Applications (VBA). Let's say I have a 4 by 2 array...(A1:B4) and I want to count the number of rows. I would think my code would be...
Function test(list) As Double
test = UBound(list)
End Function
My input is =test(A1:B4)but so far I get "#value!" error. I thought the return would be 4.
What am I doing wrong? I know how to get the number of rows using the row command but I simply want to go through the coding exercise.
List is range object not an array. Range.Value will return an array of values from a range and Range.Formula will return an array of formulas from a range.
Function test(list As Range) As Double
test = UBound(list.Value)
End Function
It seems that you have 2-dimensional array, therefore you have to provide additional parameter for UBound like:
UBound(array, dimension)
Please remember, if you get array from Excel Range than it is 1-based array.
Complete solution can look like this one:
Function testArrray(List, Dimmension)
Dim ListConverted As Variant
ListConverted = List
testArrray = UBound(ListConverted, Dimmension)
End Function
Sample call: =testArrray(G15:H20,1) produces 6 as a result which is correct.
Since you do not Dim list explicitly, it is Variant. So if you call =TEST(A1:B4) it will be a Range and not an Array. A Range does not have a UBound but does have Rows.Count.
So:
Function test(list As Range) As Double
test = list.Rows.Count
End Function
will work.
Or if you really need an Array, you could do:
Function test(list As Variant) As Double
list = list
test = UBound(list)
End Function
The list = list does the following: If list is a Range-Object then it will be implicit converted to Array since Set is not used to set an object. If list is an Array already, then it will be also an Array after that.
This is how you get the number of rows in a function.
Option Explicit
Function l_number_of_rows(rng_range As Range) As Long
l_number_of_rows = rng_range.Rows.Count
End Function
if you want the one from the dimension, this is a possible solution.
Sub test()
Dim MyArray(1 To 2, 0 To 3) As Long
MyArray(1, 0) = 10
MyArray(1, 1) = 11
MyArray(1, 2) = 12
MyArray(1, 3) = 13
MyArray(2, 0) = 20
MyArray(2, 1) = 21
MyArray(2, 2) = 22
MyArray(2, 3) = 23
Debug.Print MyArray(UBound(MyArray), 3)
End Sub
The UBound function is to give you elements in a variant array, you're passing it a range. You could try this:
Sub mains()
Dim myRange As Range, myArray As Variant
Set myRange = Range("A1:B4")
myArray = myRange.Value
Debug.Print test(myArray)
End Sub
Function test(list) As Double
test = UBound(list)
End Function
EDIT With a two dimensional range (as mentioned by KazimierzJawor) the default ubound will be the vertical, as you wanted, if you want to specify, you can add the optional perameter UBound(list, 1), but with the data you specified this defults to 4 as you wished
Ubound is a function that works on arrays you are passing it a range
try this
dim i(3)
i(0) = 1
i(1) =2
i(2) = 3
msgbox ubound(i)

Get Index of last active columns per Row in Excel using Open XML

How do i get the Index of the last active column in a row using Open Xml
i have this for row 1.
Dim activeCells As IEnumerable(Of DocumentFormat.OpenXml.Spreadsheet.Cell) = row.Descendants(Of DocumentFormat.OpenXml.Spreadsheet.Cell)().Where(Function(c) Not String.IsNullOrEmpty(c.InnerText))
Dim cell As DocumentFormat.OpenXml.Spreadsheet.Cell = activeCells.LastOrDefault()
Dim CellRef As String = cell.CellReference
This gives D1", but what i want is the index in this case "4". how do i go about this?
To convert the cell reference to a column index you could use something like the following (I've converted the code from the answer here which you've inspired me to write :)).
Private Shared Function GetColumnIndex(cellReference As String) As System.Nullable(Of Integer)
If String.IsNullOrEmpty(cellReference) Then
Return Nothing
End If
'remove digits
Dim columnReference As String = Regex.Replace(cellReference.ToUpper(), "[\d]", String.Empty)
Dim columnNumber As Integer = -1
Dim mulitplier As Integer = 1
'working from the end of the letters take the ASCII code less 64 (so A = 1, B =2...etc)
'then multiply that number by our multiplier (which starts at 1)
'multiply our multiplier by 26 as there are 26 letters
For Each c As Char In columnReference.ToCharArray().Reverse()
columnNumber += mulitplier * (CInt(c) - 64)
mulitplier = mulitplier * 26
Next
'the result is zero based so return columnnumber + 1 for a 1 based answer
'this will match Excel's COLUMN function
Return columnNumber + 1
End Function
Note: the VB might not be idiomatic as I used the Telerik Converter to convert it from C# to VB.

Custom User Function (UDF) and Dynamic Ranges

I have a Custom User Function (UDF) that returns an array. I use it in an Array Formula on a large range of cells.
The length of the returned array is dependent on the function parameters.
This works great except one thing: When the length of the returned array is smaller than the range defined for the array-formula, the "out-of-range" entries are all set to #N/A.
Is there a way to either obtain the array-formula range inside the custom user function (so, if needed, I could prepare a larger array to return), or alternatively return some kind of an iterator (instead of the array) which is not limited in size and would return "" in case of out-of-range?
Here is a pretty dumb example......a UDF to return the first 7 primes in column form:
Public Function Primes()
'
' Array UDF to return the first 7 primes
'
Dim rN As Long, ary(1 To 7) As Long
Dim tdim As Long, i As Long
Dim wf As WorksheetFunction
Set wf = Application.WorksheetFunction
rN = Application.Caller.Rows.Count
tdim = wf.Max(rN, 7)
ReDim bry(1 To tdim, 1 To 1)
ary(1) = 1
ary(2) = 3
ary(3) = 5
ary(4) = 7
ary(5) = 11
ary(6) = 13
ary(7) = 17
For i = 1 To 7
bry(i, 1) = ary(i)
Next i
If tdim > 7 Then
For i = 8 To tdim
bry(i, 1) = ""
Next i
End If
Primes = bry
End Function
The UDF detects how many cells it has to fill and if that value exceeds 7, the balance is filled with blanks.

Variables in VBA loops

I'm trying to run a VBA application using a loop and using variables whose names depends on where in the loop I am. Specifically something like
Dim i As Integer
i = 1
Dim varname() As String
while i < 50
varname(i) = asdasd
i = i + 1
Wend
Somehow it can't read varname(i) or whatever. It reports subscript out of range.
I have no idea what the problem is, can someone helt me perhaps?
You need to give your array a capacity first.
Sub max()
Dim i As Integer
i = 1
Dim varname() As String
ReDim varname(49) '<---- There
While i < 50
varname(i) = asdasd
i = i + 1
Wend
End Sub
This is a good resource for VBA arrays:
http://msdn.microsoft.com/en-us/library/office/aa164778(v=office.10).aspx

integer to string problems

I'm trying to make a slot machine program. This procedure that I'm trying to do will assign a name to 3 randomly generated numbers. For some reason I'm getting a conversion error saying that it cant convert the integer to a string. I tried cstr() as well but the problem persisted
Sub GenerateNumbers()
Dim numbers(2) As Integer
Dim names(5) As String
Dim x As Integer
names(0) = "Cherries"
names(1) = "Oranges"
names(2) = "Plums"
names(3) = "Bells"
names(4) = "Melons"
names(5) = "Bar"
For x = 0 To 2
numbers(x) = names(CInt(Int((6 * Rnd()) + 1)))
Next x
End Sub
gives me error: conversion from string "Oranges" to type 'Integer' is not valid
The problem is that you are getting a random string from the names array and trying to assign it to numbers, which is declared as an array of integers. Of course this is not gonna work.
Apart from that, there is also the issue with out of bounds index as Eric pointed out.
Edit in response to comments:
To get the text values of those randomly generated slot machine results you just need to declare the array to store results as strings, same way as names is declared.
To be able to get the results from a separate procedure, you need to change it from Sub to Function, which is a procedure that can return a value, an array of strings in this case. Then you can call this function from your Main or any other procedure and store the returned value in a variable.
I also corrected the part with random result generation.
Module SlotMachine
Sub Main()
Dim slotResults As String()
'Get the results
slotResults = GenerateResults()
'Some further processing of results here, e.g. print results to console
For Each item In slotResults
Console.WriteLine(item)
Next
'Wait for keypress before closing the console window
Console.ReadLine()
End Sub
'Generates random results
Function GenerateResults() As String()
Dim results(2) As String
Dim names(5) As String
Dim x As Integer
names(0) = "Cherries"
names(1) = "Oranges"
names(2) = "Plums"
names(3) = "Bells"
names(4) = "Melons"
names(5) = "Bar"
Randomize()
For x = 0 To 2
results(x) = names(Int(6 * Rnd()))
Next x
Return results
End Function
End Module
Int(6 * Rnd()) will get you 0-5, if you +1, then overflow