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.
Related
I have the next code:
Function findRanges(keyword) As Variant()
Dim foundRanges(), rngSearch As Range
Dim i, foundCount As Integer
i = 0
foundCount = 0
ReDim foundRanges(0)
Set rngSearch = ActiveDocument.Range
Do While rngSearch.Find.Execute(FindText:=keyword, MatchWholeWord:=True, Forward:=True) = True
Set foundRanges(i) = rngSearch.Duplicate
i = i + 1
ReDim Preserve foundRanges(UBound(foundRanges) + 1)
rngSearch.Collapse Direction:=wdCollapseEnd
Loop
ReDim Preserve foundRanges(UBound(foundRanges) - 1)
findRanges = foundRanges
End Function
And:
Sub test()
Dim rngIAM_Code() As Range
...
Dim rngIAM_Title() As Range
rngIAM_Code = findRanges("IAM_Code")
...
rngIAM_Title = findRanges("IAM_Title")
End Sub
What is very confuding is that sometimes the compiler says "Can't assign to array" and sometimes it works fine. For example, when I only try to search one value and populate one array, the code works. When I try to populate both array, there is an error "Can't assign to an array". I can then switch lines of code like this:
rngIAM_Title = findRanges("IAM_Title")
...
rngIAM_Code = findRanges("IAM_Code")
And then the error happens with another array. The error can happen anywhere: on the first line, in the middle, or in the end, but it is consistent as long as I don't move lines. And again, if I leave only one-two lines of code with arrays in sub "test"everything works fine.
The following works for me.
In this code, every object variable is explicitly assigned a type. In VBA, every variable must be typed, else it's assigned the type Variant by default. In the following declaration line, for example, foundRanges() is of type Variant because it's not followed by As with a data type. The same with i in the next line of code in the question.
Dim foundRanges(), rngSearch As Range
And since the arrays in the calling procedure are of type Range the function should return the same type.
I also took the liberty of passing the Document object to the function as, conceivably, some day the document in question might not be ActiveDocument but a Document object assigned using Documents.Open or Documents.Add. If this is not desired it can be changed back, but not relying on ActiveDocument is more reliable...
Additionally, I added the Wrap parameter to Find.Execute - it's always a good idea to specify that when executing Find in a loop to prevent the search from starting again at the beginning of the document (wdFindContinue).
Sub testRangesInArrays()
Dim rngIAM_Code() As Range
Dim rngIAM_Title() As Range
rngIAM_Code = findRanges("You", ActiveDocument)
rngIAM_Title = findRanges("change", ActiveDocument)
End Sub
Function findRanges(keyword As String, doc As Word.Document) As Range()
Dim foundRanges() As Range, rngSearch As Range
Dim i As Integer, foundCount As Integer
i = 0
foundCount = 0
ReDim foundRanges(0)
Set rngSearch = doc.content
Do While rngSearch.Find.Execute(findText:=keyword, MatchWholeWord:=True, _
Forward:=True, wrap:=wdFindStop) = True
Set foundRanges(i) = rngSearch.Duplicate
ReDim Preserve foundRanges(UBound(foundRanges) + 1)
i = i + 1
rngSearch.Collapse Direction:=wdCollapseEnd
Loop
findRanges = foundRanges
End Function
Here is an alternative based on a Collection instead of an Array:
I used also included Cindys Input regarding passing the document and adding wrap.
I don't exactly know what the you use the return value for, but in general a collection is a bit more flexible than an Array.
I also removed the underscores since they indicate a function of an implemented Interface and may cause problems later down the line. are used when implementing an Interface (improves readability).
As explained here you can use wrap or collapse to prevent a continuous Loop.
Option Explicit
Sub test()
Dim rngIAMCode As Collection
Dim rngIAMTitle As Collection
Set rngIAMCode = findRanges("IAM_Code", ActiveDocument)
Set rngIAMTitle = findRanges("IAM_Title", ActiveDocument)
Debug.Print "Code found : " & rngIAMCode.Count & " Times."
Debug.Print "Title found : " & rngIAMTitle.Count & " Times."
End Sub
Function findRanges(ByVal keyword As String, doc As Document) As Collection
Set findRanges = New Collection
Dim rngSearch As Range
Set rngSearch = doc.Content
With rngSearch.Find
.Text = keyword
.MatchWholeWord = True
.Forward = True
.Wrap = wdFindStop
While .Execute
findRanges.Add rngSearch.Duplicate
rngSearch.Collapse Direction:=wdCollapseEnd
Wend
End With
End Function
I'm trying to create a program that optimizes histogram bin width...before that though, I need help with a seemingly simple task - setting a Range mentioned in a cell into a VBA array.
I'd like the data for the histogram to be on any sheet, in this case 'Data'!B4:M12. This is mentioned in D4 of the sheet I want the histogram to appear on. I keep getting an error when I run my code though, even after changing it multiple times. There's clearly a syntax error that I don't know how to handle.
Any assistance would be much appreciated!
Sub Histogram_Shimazaki_Shinomoto()
Dim data_range As String, min_bins As Integer, max_bins As Integer
Dim Data()
Dim x_min As Double, x_max As Double
data_range = Cells(4, 4) ' data range
min_bins = Cells(5, 4) ' min # of bins
max_bins = Cells(6, 4) ' max # of bins
Set Data = Range(data_range)
x_min = WorksheetFunction.Min(Data)
MsgBox x_min
End Sub
You are trying to set an array of variant equal to a Range -- but that isn't possible since you can't assign to an array. You could assign a range to a simple Variant (or to a Range variable). You could change
Dim Data()
to
Dim Data as Variant
Note the absence of parenthesis. Also, as a stylistic point I think that it is good to be explicit about the type, even though Variant is the default.
This might be enough for your code to work, although if all you want is the minimum value in the range, you could change
Set Data = Range(data_range)
to
Data = Range(data_range).Value
If the sheet "Data" isn't the active sheet and data_range contains "B4:M12" then you would need to use
Data = Sheets("Data").Range(data_range).Value
since Range returns a range on the active sheet unless explicitly qualified by a reference to the sheet.
I'm very new to VBA, to bear with me here.
I want to assign a set of variables the value of a set of ranges ie. run a brief code to simplify the following
Dim Sample 1 as string
Sample1 = activeworksheet.range("C17").value
Dim Sample 2 as string
Sample2 = activeworksheet.range("C18").value}
and so on
Following an excelfunctions.net tutorial, I know that I can shorten the declaration to
Dim Sample(1 to 20) as a string
But the tutorial drops it there(because it's a tutorial about names), suggesting I populate it as follows
sample(1)=activesheet.range("C7").value
sample(2)=activesheet.range("C7").value
and so on
I found the discussion below to be on the right track to answer my quest, but I am having trouble applying it to my situation. (Excel VBA Array Ranges for a loop)
As a follow up note, I am ultimately trying to assign values to these variables for use in the following procedures, rather than declaring and assigning them each time.
Thanks!
Try something like this:
Sub test()
Dim sampleArr(1 To 20) As String
Dim i As Integer
Dim rng As Range, cel As Range
i = 1
Set rng = Range("C1:C20")
For Each cel In rng
sampleArr(i) = cel.Value
i = i + 1
Next cel
For i = LBound(sampleArr) To UBound(sampleArr)
Debug.Print sampleArr(i)
Next i
Also, if you know the range you want to put into an array, you can simply set an array to that range:
Sub test()
Dim sampleArr() As Variant
Dim i As Integer
Dim rng As Range, cel As Range
i = 1
Set rng = Range("C1:C20") ' Note, this creates a 2 Dimensional array
sampleArr = rng ' Right here, this sets the values in the range to this array.
For i = LBound(sampleArr) To UBound(sampleArr)
Debug.Print sampleArr(i, 1) ' you need the ",1" since this is 2D.
Next i
End Sub
You should :
Define the range you want to retrieve data
For each cell of the range, retrieve your datas
dim tab() As string, cell as range, i as integer
i = 0
redim tab(0)
for each cell in ActiveWorksheet.Range("C1:C20")
tab(i) = cell
i = i + 1
redim preserve tab(i)
next
edit : I indent the code to display it correctly
Additional way to the above you can only use:
Arr = ActiveWorksheet.Range("C1:C20").Value
Then you can directly use:
Arr(i,1) where i is C1 to C20 range!
I would like to count the number of matching items in an array. I tried using
Application.Countif
MyCount = Application.WorksheetFunction.CountIf(Myrange, val)
but this returns an array full of errors rather than a simple count. I have also tried using Application.WorksheetFunction.Countif but this causes a 424 error.
I currently am testing on a worksheet with a short list of names in cells A1:A20, but ultimately I plan to use this code with a very large CSV file and I want to load the information into an array before using CountIf (rather than using the range).
Sub TestCount()
Dim MyCount
Dim Myrange As Variant
Dim val As String
val = "Addison"
Myrange = ActiveSheet.Range("A1").CurrentRegion.Value
MyCount = Application.WorksheetFunction.CountIf(Myrange, val)
MsgBox (MyCount)
End Sub
Can anyone suggest what I did wrong?
You have several problems.
Using CountIf
First, if I understand right, you are intentially trying to use the Application.WorksheetFunction.CountIf statement on an array. That will only cause trouble, since CountIf (as the statment suggests) is a "worksheet function" not a "VBA Array function".
Ditto has created a solution that uses CountIf correctly, by setting a range in the worksheet on which the CountIf statement performs its job. If all you want is a way to count the value within that range, that is the way to go.
Creating an array from a range
Second, if you really need to get the items out of the worksheet and into an array (for example, if you plan to work with those values in ways you don't want to effect the worksheet), you should know that you have only partially solved the question of creating an array of values from a range selection.
You are correct that to establish an array by assigning a range to a variable you need a variant, but you have forgotten the parenthesis, which are an essential part of denoting an array.So, instead of Dim Myrange As Variant you should use Dim Myrange () As Variant
Having established MyRange as an array, you can now assign the array values by saying MyRange = Range("x") where x is the area being captured. You do not need to (or want to) use .Value for this. VBA will automatically do that for you. So, in your case you want to use the CurrentRegion for Range("A1") which is done like this: MyRange = Range("A1").CurrentRegion. You could also use a closely defined range like this: MyRange = Range("A1:A12") or MyRange = Range("C7:F14"). Note: I left off the ActiveSheet because it does not work when assigning ranges to arrays. The assumption is that you are using the active sheet, and the current region is for the cell indicated in the Range("x") statement.
Counting values within the array
Third, once you have succeeded in creating an array, you won't be able to use Countif (as noted above). You'll need to create a method of counting that value within the array. There are several considerations in doing this.
Since an array created from a range will be two dimensional and may have more than one column, you should not assume just one column. You will want to create a variable that holds the number of rows and number of columns, so you can loop through the entire array. Something like this:
Dim Row As Long
Dim Col As Long
You will want to define the limits of your loops using the UBound of the array dimensions. Something like this:
Dim RowNumber As Integer
RowNumber = UBound(MyRange, 1)
Dim ColNumber As Integer
ColNumber = UBound(MyRange, 2)
Code for using an array to find your count
I think the following code will do what you want using an array created in the manner you were attempting:
Sub TestCount()
Dim MyCount As Long
MyCount = 0
Dim MyRange() As Variant
MyRange = Range("A1").CurrentRegion
Dim val As String
val = "Addison"
Dim Row As Long
Dim Col As Long
Dim RowNumber As Long
RowNumber = UBound(MyRange, 1)
Dim ColNumber As Long
ColNumber = UBound(MyRange, 2)
For Col = 1 To ColNumber
For Row = 1 To RowNumber
If MyRange(Row, Col) = val Then MyCount = MyCount + 1
Next Row
Next Col
msgbox MyCount
End Sub
Just because this horse hasn't been beat enough already..here is a 1 liner
Sub Button3_Click()
MsgBox Application.WorksheetFunction.CountIf(Range("A1:a20"), "Addison")
End Sub
Try this:
Sub TestCount()
Dim MyCount
Dim Myrange As Range
Dim val As String
val = "Addison"
Set Myrange = ActiveSheet.Range("A1:a20")
MyCount = Application.WorksheetFunction.CountIf(Myrange, val)
MsgBox (MyCount)
End Sub
1) define "Myrange" as a RANGE, not a variant.
2) use "set" keyword to assign range to Myrange
3) give it the range you want: "a1:a20", not just "a1"
Yes, you didn't declare you range as a range type, so you didn't set the range.
Sub Macro1()
Dim val as String
Dim r As Range
Set r = Range("a1:a20")
val = "Addison"
MsgBox Application.WorksheetFunction.CountIf(r, val)
End Sub
or
Sub CritSrh_Column()
Dim cell As Variant
Dim counter As Integer
For Each cell In Range("A1:A20")
'could use ("A:A") to search the whole column #not recommended#
'for dynamic rows, use end.xl('direction')
If cell.Value = "Addison" Then
counter = counter + 1
End If
Next
MsgBox counter
End Sub
First off, I appreciate any help anyone can offer. I am writing a macro that will give the user a form to input a number key. The form will search a spreadsheet for the key and return the corresponding name attached to that key. The data may have multiple names per key and it will vary depending on the key. I want to loop through the data with .Find and .FindNext, and find all the possible names attached to that key (i have accomplished this part). The part I am having trouble with is during the loop, storing each name in an array that I can pass to another sub. I want to pass the array so that the user can click another command button and cycle through the possible names before choosing one.
Private Sub FindNameButton_Click()
Dim KeyMatch As Long
Dim NameRow As Long
FindName As Range
KeyMatch = KeyBox.Value ' The UserForm input box
With Worksheets("Master List"). Range("D:D")
Set FindName = .Find(What:= KeyMatch, LookAt:= xlWhole, LookIn:= xlValues, MatchCase:= False)
If not FindName Is Nothing Then
FirstAddress = FindName.Address
Do
Application.GoTo FindName
NameRow = ActiveCell.Row
Cells(NameRow, 2).Select 'Selects the name associated with the key identifier
NameBox.Value = ActiveCell.Value 'Fills the UserForm box with the name
' I would like to fill the array here with each name is it passes through but I have no idea how
NameArray(i) = ActiveCell.Value ' ??????
Set FindName = .FindNext(FindName)
Loop While FindName is Nothing and FristAddress <> FindName.Address
End With
End Sub
Private Sub NextNameButton_Click()
Static cnt As Long
If cnt <= Ubound(NameArray) Then
NameBox.Value = NameArray(cnt) 'Fill UserForm Name Box with name from Name Array
Else
cnt = 0
End If
cnt = cnt + 1 ' increase every time button is clicked
End Sub
Your question could use additional details about the problem. A few things I noticed.
You are missing an 'End If' for 'If not FindName Is Nothing Then'
NameArray isn't passed out or into your subroutines. Have you decared NameArray as global?
NameArray needs to be declared as a dynamic array: Dim NameArray() As Variant.
You need to use 'Redim Preserve NameArray(newIndxBound)' to increase the size of an array.
I recommend using 'Option Explicit' to make sure all your variables have been defined.
You might consider using the function StrCmp for string comparison instead of 'FristAddress <> FindName.Address'.
This bit of code that used a global dynamic array might help you out.
Option Explicit
Public MyArray() As Variant
Sub AddToArray()
Dim indx As Integer
For indx = 0 To 9
ReDim Preserve MyArray(indx)
MyArray(indx) = indx
Next indx
End Sub
Sub RetrieveFromArray()
Dim indx As Integer
Dim sht As Worksheet
Dim rowN As Integer
Set sht = ActiveSheet
rowN = 10
For indx = 0 To 9
sht.Cells(rowN, 3) = MyArray(indx)
rowN = rowN + 1
Next indx
End Sub