Assigning a Range to an Array is pretty simple. Still, I found a case in which VBA behaved unexpectedly. I could not find an answer why that is and so I hope someone can explain to me, why it is not working.
Task: Assign a Range (from an open Workbook) to an Array
Working Code
Dim vrtTabOEen () as Variant
Dim rngTabOEen as Range
With ThisWorkbook.Worksheets(Name_AB_Tab_Def_OEen)
Set rngTabOEen = .Range(Name_Tab_Def_OEen)
vrtTabOEen = rngTabOEen
End With
Non-Working Code
Dim vrtTabOEen () as Variant
With ThisWorkbook.Worksheets(Name_AB_Tab_Def_OEen)
vrtTabOEen = .Range(Name_Tab_Def_OEen)
End With
Using the non-working code, I'll get error 13: Type mismatch.
Question
Why do I have to assign the target range to a variable of type 'range' before creating an array out of it?
You have too many levels of indirection for the implicit coercion from Variant to Variant array to work, due to the fact that Worksheets returns a generic Object and you didn't specify the value property, which you should always do. Either:
Dim vrtTabOEen () as Variant
With ThisWorkbook.Worksheets(Name_AB_Tab_Def_OEen)
vrtTabOEen = .Range(Name_Tab_Def_OEen).Value
End With
or:
Dim vrtTabOEen as Variant
With ThisWorkbook.Worksheets(Name_AB_Tab_Def_OEen)
vrtTabOEen = .Range(Name_Tab_Def_OEen)
End With
should work.
Related
I am working through an Access 2010 VBA script. I am pulling information from a temporary table that may have 1 to 10 records in it. I am using GetRows to build a two-dimensional array that looks like this, for example:
**0** **1**
8677229 1
10289183 2
11981680 3
13043481 4
I have tested the array function by debug print, and the array does contain values. I want to split out the array values dynamically into variables for later use. In this example, I would like the 0 column to produce 4 variables named Anum(0) through Anum(3), and the 1 column to produce 4 variables named Pay(0) through Pay(3). However, I keep getting a "Error 9, subscript out of range" where indicated below:
Dim db As Database
Dim rs As Recordset
Dim PayAcct As Variant
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT Anum, Pay FROM PayPerAcct")
Dim highval As Integer
Dim i As Integer
i = 0
While Not rs.EOF
PayAcct = rs.GetRows(10)
Wend
highval = UBound(PayAcct, 2)
Dim Anum() As Variant
Dim Pay() As Variant
For i = 0 To highval
'error occurs on the below line
Anum(i) = CStr(PayAcct(0, i))
Pay(i) = CStr(PayAcct(1, i))
Next i
When I manually define Anum and Pay variables and use the Cstr(PayAcct(1,0)) operation, it passes the expected value from the array to the variable, so I don't think that is the problem.
I suspect I am assigning Anum() and Pay() the wrong dimension, because I have seen similar example code in Excel VBA where defining those variables as Range works. I have tried defining Anum() and Pay() as Range (which doesn't work, because this is Access VBA), Variant, and Object.
Any thoughts or tips?
Edit -
The below ended up working, thanks for your help:
Dim Anum() As String
ReDim Anum(0 To highval)
Dim Pay() As String
ReDim Pay(0 To highval)
Dim Anum() As Variant
declares a dynamic array that hasn't any elements yet, so you can't assign values to it. It needs a ReDim to use it.
But since you already know how many elements you need, what you want is:
Dim Anum(0 To highval) As Variant
or if you know that you will only store strings in it,
Dim Anum(0 To highval) As String
RsProxyList.Open objDBCommand,,1,1
dim recCount:recCount = RsProxyList.RecordCount
Dim output(recCount,2)
I get an error because recCount is of wrong type. I have tried to convert it to Int but that does not work either. The following works fine:
RsProxyList.Open objDBCommand,,1,1
dim recCount:recCount = RsProxyList.RecordCount
Dim output(3,2)
How do I convert recCount to get this array declaration to work?
You need to first declare your Array as dynamic then use ReDim to set the first dimension dynamically, like this;
Dim output() 'Declare a "Dynamic Array"
ReDim output(recCount, 2) 'Set dynamic first dimension.
By specifying the dimensions like this;
Dim output(10, 2) 'Declare a "Fixed Array"
you tell VBScript to build a fixed array which does not accept variables as dimension declarations.
Dim output() versus Dim output
There seems to be some argument as to using
Dim output()
versus
Dim output
#Bond in the comments below came up with the most convincing one in my opinion for using Dim output().
Because VBScript stores all variables as Variant data type declaring an Array using Dim output is just like declaring any other variable, whereas Dim output() creates an Array (0 sized array but an array nonetheless), this means there is one significant difference as far as the VBScript Runtime is concerned, Dim output() is an Array.
What does this matter?, well it matters because functions like IsArray() will still detect the variable as an Array where as "arrays" declared using Dim output will return False.
Useful Links
#DougGlancy's answer to Redim without Dim? (vba but still relevant).
VBScript Reference - Dim Statement
VBScript Reference - VBScript Variables (specifically Array Variables sub heading).
I'm hoping to load values in a range to an array and transpose that array to another location (different workbook)
I am using the below forum post to get an idea of how to do it:
http://www.mrexcel.com/forum/excel-questions/629320-application-transpose-visual-basic-applications-array.html
Below is the code I am working with now, and I'm getting the 1004 object defined error. Can anyone spot what I am doing wrong?
I did find that the code works if I do not Set tRangeArray and instead do Sheets("sheet1").Range("C12:C19).Value = Application.Transpose(MyArray), but I'm not sure why that's different from my code.
Sub copy_data()
Dim cRange As Range, aRange As Range, tRange1 As Range, wbk1 As Workbook, wbk2 As
Workbook
Dim MyArray() As Variant, tRangeArray As Range
Set wbk1 = ThisWorkbook
MyArray = Range("E12:L12")
Set tRangeArray = wbk1.Sheets("sheet1").Range("C12:C19")
Sheets("sheet1").Range(tRangeArray).Value = Application.Transpose(MyArray)
As I mentioned in comments, just use:
tRangeArray.Value = Application.Transpose(MyArray)
Sheets("sheet1").Range(tRangeArray).Value not working, because Range accepts either single parameter - string with range address (not range itself): Range(addr), either two parameters - top left and bottom right cells: Range(cell_1,cell_2)
Similar, but using Resize and Ubound:
Dim myarray As Variant
myarray = Array(1, 2, 3, 4, 5)
Sheets("sheet1").Range("A1").Resize(UBound(myarray), 1).Value = Application.Transpose(myarray)
I want to have an array of ranges to create charts from them.
Here's how I had it:
Dim infoR As Range
Dim aRng() As Range
Dim numLvls As Integer
Set infoR = Range("H1:H100");
numLvls = getLevels()
Set aRng() = getOnlyNumericCellToArrayRanges(infoR, numLvls)
The function is this:
Function getOnlyNumericCellsRangesArrays(ByVal actRange As Range, ByVal numLvls As Integer) As Range()
Dim aRng() As Range
Redim aRng(0 To numLvls - 1)
'Some code
Set getOnlyNumericCellToArrayRanges = aRng()
End Function
I've seen several arrays examples over the internet and they use variant as a data type for that means but it doesn't compile like that too.
I've found that works with some changes:
Dim aRng
'Some code
aRng = getOnlyNumericCellToArrayRanges(infoR)
I think passing the array by reference could work, however I want to know if there is a way to make the array declaration and assignment to Range data type explicitly from the beginning.
Or how can I cast the result array back into a Range array?
An array is not an object (even when it's an array of objects), so you don't need Set here...
Sub Tester()
Dim arrRng() As Range, x As Long
arrRng = GetRangeArray()
For x = LBound(arrRng) To UBound(arrRng)
Debug.Print arrRng(x).Address()
Next x
End Sub
Function GetRangeArray() As Range()
Dim arrRng() As Range
ReDim arrRng(1 To 3)
Set arrRng(1) = ActiveSheet.Range("A1")
Set arrRng(2) = ActiveSheet.Range("A3")
Set arrRng(3) = ActiveSheet.Range("A5")
GetRangeArray = arrRng
End Function
I have converted a range to an array and then I want to loop through each value to count how many no empty values there are.
For some reason this was working fine
Option Explicit
Sub ArrayCount ()
Dim cmArray As Variant
Set cmArray = Worksheets("Sheet1").Range("H19:CN19")
ColCount = 1
For Each Value2 in cmArray
If Value2 <> "" Then
ColCount = ColCount + 1
End If
Next
End Sub
It all seemed simple enough to me and worked well. Now all of a sudden I am getting an error saying "Compile Error: Variable not defined" and it is highlighting Value2. Is anyone able to explain what is happening here or talk me through what may have happened?
Thanks in advance
It looks like you're trying to put the Value property of a Range object in an array and loop through that array. That's not what you're doing and there are a few concepts at play here.
Default Properties
When you reference an object and don't specify a property, you are actually accessing its default property. For most objects, if it has a Value property, that's the default. And if it has an Item property, that's the default. The Range object is a little weird. Most times the Value property is the default property of a Range. In some cases, the Cells property is the default. If you say
vValue = rMyRange
You are setting vValue equal to the Value property. Make it a habit to always include a property rather than rely on the default.
vValue = rMyRange.Value
The Set Keyword
The Set keyword stinks. But because of default properties, it is necessary. When you use Set, you're saying you want an object variable to point to an object. If you don't use Set, you're saying you want a data variable to be equal to a value.
vValue = rMyRange 'vValue = rMyRange.Value
Set vValue = rMyRange 'vValue points to the range object
vValue = rMyRange.Value 'same as the first, good programming practice
Set vValue = rMyRange.Value 'you get an error - a good reason to specify property
Variants
Excel has a Variant data type that can be cast into any of the normal data types. This is because Excel cells can hold a lot of different types of data and they needed something with similar flexibility. If you want to read the value of a cell and can't be sure what will be in it, use a Variant. There are a few other times to use a Variant (like reading a range into an array). In general, don't use a Variant unless you have a very specific (and good) reason to.
Arrays from Cells
If you assign the Value or Value2 property of a Range to a Variant variable, and the Range has more than one cell, the variable will be cast as a two-dimensional array. It's the most awesome way to read Excel data and work with it - speed wise. But you have to start with a Variant so you have to be extra careful about the above concepts.
vVariant = rRange.Value 'creates an array in vVariant
Set vVariant = rRange.Value 'causes an error
Set vVariant = rRange 'vVariant gets cast to a Range object - not what you wanted
So use the first syntax. Then you can use For i = LBound(vVariant,1) to UBound(vVariant,1) to loop through. Or you can use For Each vItm in vVariant.
Worksheet Function Way
Sub ArrayCount()
Dim rRow As Range
Dim lColCnt As Long
Set rRow = Sheet1.Range("H19:CN19")
lColCnt = Application.WorksheetFunction.CountA(rRow)
Debug.Print lColCnt
End Sub
Array Way
Sub ArrayCntArray()
Dim cmArray As Variant
Dim i As Long
Dim lColCnt As Long
cmArray = Sheet1.Range("H19:CN19").Value
For i = LBound(cmArray, 2) To UBound(cmArray, 2)
If Len(cmArray(1, i)) > 0 Then
lColCnt = lColCnt + 1
End If
Next i
Debug.Print lColCnt
End Sub
Range Object Way
Sub ArrayCountRange()
Dim rRow As Range
Dim rCell As Range
Dim lColCnt As Long
Set rRow = Sheet1.Range("H19:CN19")
For Each rCell In rRow.Cells
If Len(rCell.Value2) > 0 Then
lColCnt = lColCnt + 1
End If
Next rCell
Debug.Print lColCnt
End Sub
You are using Option Explicit, which means all variables should be dimensioned. Simply adding Dim Value2 as Variant at the dimension area should be enough.
Option Explicit
Sub ArrayCount ()
Dim cmArray As Variant
Dim Value2 As Variant
'Code follows...
As a side note, Value2, while I don't know if it's a reserved keyword, is a property. Try changing it to something else as well to be safe or to avoid confusion.
Hope this helps.