I have an array that is populated if a formula produces an "X" in a cell that is part of a range:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Fault(10) As Boolean
For i = 1 To 10
If Range("A" & i).Value = "X" Then
Fault(i) = True
End If
Next i
MsgBox Fault 'VBA Errors Here With "Type Mismatch"
End Sub
My question is, is it possible to return an entire array as a string. So in the above example, I want the message box to return "0000000000" if there were no faults. If there was a fault in the 7th array, then it would return "0000001000".
My aim is to check that the string is always equal to "0000000000" in order to proceed. However, if there's a better way of checking if the entire array is false then that would be much easier.
Try this:
Sub JoinArray()
Dim Fault(9) As String, arrString As String
For i = 1 To 10
If Range("A" & i) = "X" Then
Fault(i - 1) = 1
Else
Fault(i - 1) = 0
End If
Next i
arrString = Join(Fault(), "")
If InStr(arrString, "1") Then
MsgBox "Fault Found"
Else
MsgBox "No faults found"
End If
End Sub
Notes:
Typically an array is zero indexed so Fault(9) allows for 10 slots e.g. Range("A1:A10")
The "" argument of Join means there are no space in the output i.e. 0011000000
Alternative method without using an array
Sub FindFaults()
Dim rng As Range, cl As Range, faultLocations As String
Set rng = Range("A1:A1000")
faultLocations = "Faults found in the following cell(s):" & vbCrLf & vbCrLf
If WorksheetFunction.CountIf(rng, "X") = 0 Then
MsgBox "No Fault Found"
Else
For Each cl In rng
If cl = "X" Then
faultLocations = faultLocations + "Cell: " & cl.Address & vbCrLf
End If
Next cl
End If
MsgBox faultLocations
End Sub
Related
I am creating a text-box using an array so that when someone searches a value in 1 column, they get the results from the other 3. For some reason, although the time for "CallTime"/"RequestedCallTime" is formatted "h:mm:ss" on excel, it is coming through as a decimal in the textbox.
Private Sub Workbook_Open()
Dim PostalCode() As String
Dim CallReason() As String
Dim CallDate() As String
Dim CallTime() As String
Dim wsdata As Worksheet
Dim i As Integer
Dim found As Boolean
Dim requestedCode As String
Dim requestedCallReason As String
Dim requestedCallDate As String
Dim requestedCallTime As String
Dim nProducts As Integer
Set wsdata = Worksheets("Toronto311Data")
' Find the number of products, redimension the arrays, and fill them
' with the data in the lists.
With wsdata.Range("A1")
nProducts = Range(.Offset(1, 0), .End(xlDown)).Rows.Count
ReDim PostalCode(1 To nProducts)
ReDim CallReason(1 To nProducts)
ReDim CallTime(1 To nProducts)
ReDim CallDate(1 To nProducts)
For i = 1 To nProducts
PostalCode(i) = .Offset(i, 0).Value
CallReason(i) = .Offset(i, 2).Value
CallDate(i) = .Offset(i, 4).Value
CallTime(i) = .Offset(i, 5).Value
Next
End With
' Get a postal code from the user.
requestedCode = InputBox("Enter the first 3 digits of the postal code (UpperCase Letters Please).")
' Look for the code in the list. Record its unit price if it is found.
found = False
For i = 1 To nProducts
If PostalCode(i) = requestedCode Then
found = True
requestedCallReason = CallReason(i)
requestedCallDate = CallDate(i)
requestedCallTime = CallTime(i)
Exit For
End If
Next
' Display an appropriate message.
If found Then
MsgBox "The call reason of postal code " & requestedCode & " is " & requestedCallReason & ". The call date/time is " & requestedCallDate & " and the call time is " & requestedCallTime & ".", vbInformation, "Information found"
Else
MsgBox "The Postal Code " & requestedCode & " is not on the list.", _
vbInformation, "Information not found"
End If
End Sub
It might be that the spreadsheet is converting the time to a unix style time which is seconds since 1 January 1970. You might want to force the format of those cells to be a string.
Dates are numeric values. Simply format the cells value.
CallTime(i) = Format(.Offset(i, 5).Value, "h:mm:ss")
I have a vast list of data in a worksheet (called MainDump). I have a procedure set up to assess this list and return certain values using the following setup:
Dim ws1 As Worksheet
Set ws1 = Worksheets("DashBoard")
Dim ws2 As Worksheet
Set ws2 = Worksheets("MainDump")
Dim cntr As Long
On Error GoTo ErrorHandler 'Got A lot of divide by zero errors if searchstring wasn't found
With Application.WorksheetFunction
ws1.Range("O4").Value = .CountIf(ws2.Range("E:E"), "*" & "CEOD" & "*")
ws1.Range("L4").Value = .CountIfs(ws2.Range("E:E"), "*" & "CEOD" & "*", ws2.Range("A:A"), "Yes") / ws1.Range("O4").Value
ws1.Range("M4").Value = .CountIfs(ws2.Range("E:E"), "*" & "CEOD" & "*", ws2.Range("B:B"), "Yes") / ws1.Range("O4").Value
ws1.Range("N4").Value = .CountIfs(ws2.Range("E:E"), "*" & "CEOD" & "*", ws2.Range("C:C"), "SA Present, WBDA Present") / ws1.Range("O4").Value
End With
cntr = cntr + 1
'^This proces is then copied and thus repeated a total of 76 times, as I want to check
'for 76 different values in ws2.Range("E:E"), resulting in a massive code
ErrorHandler:
If Err.Number = 6 Then
If ws1.Range("O" & cntr).Value = 0 Then
ws1.Range("L" & cntr).Value = "div. by zero"
ws1.Range("M" & cntr).Value = "div. by zero"
ws1.Range("N" & cntr).Value = "div. by zero"
End If
End If
Resume Next
I wrote this when I was a lot less experienced in VBA. Needless to say this code takes a lot of time to complete (Maindump counts about 98000 rows).
So I wanted to try do this work via an array.
My approach would be to define a counter for each string I want to check in the array indexes and then looping through the array and increment the corresponding counters when a string is found in the Array. My question is if there is a way to write that loop in the following form:
Dim LastRow1 As long
Dim DataArray() As Variant
Dim SearchString1, SearchString2, .... SearchString76 As String
Dim SearchString1Cntr, SearchString2Cntr, .... SearchString76Cntr As long
With ws2
LastRow1 = .Cells.Find(What:="*", After:=.Range("A1"), LookAt:=xlPart, LookIn:=xlFormulas, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious, MatchCase:=False).Row 'Gets the total row amount in the sheet
DataArray = .Range("A3:E" & LastRow1) 'puts selected range in Array
End With
For LastRow1 = Lbound(DataArray, 1) to Ubound(DataArray, 1)
'Start a For Each loop to check for all 76 strings
If Instr(1, DataArray(LastRow1, 5), SearchString > 0 Then 'SearchString is found so then
SearchStringCntr1 = SearchStringcntr1 + 1
'Where SearchStrinCntr1 is the counter related to the string checked for in the loop,
'so it switches when the SearchString changes
End If
'Next SearchString to check
Next LastRow1
So I want to try and use a flexible If statement in a For Next loop which checks the Array index for each SearchString and then increments the corresponding SearchStringCntr if the SearchString is found in the index, before looping to the next index. Is this possible? I would like to prevent making 76 different If/ElseIf statements for each SearchString + StringCntr and then use a counter to loop through them every time the code loops through the For LastRow1 / Next LastRow1 loop. Would love to hear your input.
Maybe this will help (might need some adjustments).
Create named range "Strings" somewhere in your workbook where you'll store all your strings that you're looking for
Option Explicit
Sub StringsCompare()
Dim LastRow1 As Long
Dim DataArray() As Variant, StringArray() As Variant
Dim Ws2 As Worksheet
Dim CompareStringsNo As Long, StringCounter As Long
Dim i As Long, j As Long
Dim aCell As Range
Dim SourceStr As String, SearchStr As String
Set Ws2 = ThisWorkbook.Sheets("Sheet1")
StringCounter = 1
With Ws2
'fill array with your strings to compare
CompareStringsNo = .Range("Strings").Rows.Count
ReDim StringArray(1 To CompareStringsNo, 1 To 2)
For Each aCell In .Range("Strings")
StringArray(StringCounter, 1) = aCell.Value
StringCounter = StringCounter + 1
Next aCell
'fill data array
LastRow1 = .Cells.Find(What:="*", After:=.Range("A1"), LookAt:=xlPart, LookIn:=xlFormulas, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious, MatchCase:=False).Row 'Gets the total row amount in the sheet
DataArray = .Range("A1:E" & LastRow1)
End With
'search data array
For i = LBound(DataArray, 1) To UBound(DataArray, 1)
SourceStr = DataArray(i, 5)
'search array with your strings
For j = LBound(StringArray) To UBound(StringArray)
SearchStr = StringArray(j, 1)
If InStr(1, SourceStr, SearchStr) > 0 Then
'if match is found increase counter in array
StringArray(j, 2) = StringArray(j, 2) + 1
'you can add exit for here if you want only first match
End If
Next j
Next i
For i = LBound(StringArray) To UBound(StringArray)
Debug.Print StringArray(i, 1) & " - " & StringArray(i, 2)
Next i
End Sub
I think the main task is being over-complicated.
To check how many times a string occurs within an array you could use a function like this:
Function OccurWithinArray(theArray As Variant, stringToCount As String) As Long
Dim strArr As String
strArr = Join(theArray, " ")
OccurWithinArray = (Len(strArr) - Len(Replace(strArr, stringToCount, _
vbNullString, , , vbTextCompare))) / Len(stringToCount)
End Function
...and a demonstration:
Sub Demo()
Dim test(1 To 3) As String
test(1) = "I work at the Dog Pound."
test(2) = "I eat dogfish regularly."
test(3) = "Steroidogenesis is a thing."
Debug.Print OccurWithinArray(test, "dog")
End Sub
How it works:
Join joins all the elements of the array into one big string.
Len returns the length of the text.
Replace temporarily replaces the removes all occurrences of the search term.
Len returns the "modified" length of the text.
The difference between the two Len's, divided by the length of the string being searched for, is the number aof occurrences of the string within the entire array.
This returns 3 since the search is case-insensitive.
To make the search case-sensitive, remove the word vbTextCompare (in which case this example would return 2.)
In general I have a good macro for change management for a single value and now can write one for an multi dimensional array but need to be able to differential between without the use of error handling.
Is there any other work around for when the target is only one cell? the error handling below handles the issue but I consider it to be "sloppy."
Suggestions are appreciated on a better method.
Sub Dims(target As Variant)
Dim varData As Variant
Dim i As Integer
Dim j As Integer
varData = target
On Error GoTo Err
For i = 1 To UBound(varData, 1)
For j = 1 To UBound(varData, 2)
Debug.Print i, j, varData(i, j)
Next j
Next i
Err:
If Err.Number = 13 Then
Debug.Print target.Value
ElseIf Err.Number <> 0 Then
MsgBox "Error " & Err.Number & " just occured."
ElseIf Err.Number <> 13 And Err.Number <> 0 Then
Debug.Print "Err No.= "; Err.Number
Else
Debug.Print "No Error"
End If
End Sub
I set up a if then statement to run one way if target.count =1 and another if target.count>1
My vba code for a bunch of large ranges uses worksheetfunction.vlookup to find needed values. Ranges can be upwards of 25,000 cells, however, so this takes forever. Is there an equivalent function for arrays?
I've seen lots of SO answers that seem to address returning true/false in there is an exact string match. I need the string's location.
How about this ...
Function MyVLook(Arg As Range, Target As Range, ColIdx As Integer) As Range
Dim Idx As Integer
If Arg = "" Then
Set MyVLook = [ParamNothing]
Else
For Idx = 1 To Target.Rows.Count
If Target(Idx, 1) = Arg Then
If ColIdx < 0 Then
Set MyVLook = Target(Idx, 1).Offset(0, ColIdx)
Else
Set MyVLook = Target(Idx, ColIdx)
End If
Exit For
End If
Next Idx
End If
End Function
[ParamNothing] is a single cell range in a worksheet containing some application-specific text; otherwise this works almost like a normal VLOOKUP ... you can specify negative column offsets though (something I often miss in regular VLOOKUP), and I didn't built in a flag for range searches.
If you're only looking for the first occurrence try this:
Public Sub FindInRange()
Dim sValueToFind As String
Dim rRangeToSearch As Range
Dim rFoundRange As Range
sValueToFind = "The value I'm searching for"
With ThisWorkbook.Worksheets("Sheet1")
Set rRangeToSearch = .Range("A1:A1193")
Set rFoundRange = rRangeToSearch.Find( _
What:=sValueToFind, _
After:=rRangeToSearch.Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not rFoundRange Is Nothing Then
MsgBox sValueToFind & " found in cell " & rFoundRange.Address & _
" and the value two cells to the right is " & rFoundRange.Offset(, 2), vbInformation + vbOKOnly
Else
MsgBox sValueToFind & " not found.", vbInformation + vbOKOnly
End If
End With
End Sub
This will find an exact match due to LookAt:=xlWhole and will not match the case due to MatchCase:=False. If you want to find the last occurrence use SearchDirection:=xlPrevious.
This mimics using Ctrl + F on the worksheet.
For more info on VBA FIND see: https://msdn.microsoft.com/en-us/library/office/ff839746.aspx
If I declare a dynamic sized array like this
Dim myArray()
Then how I can get in the code if this array is empty or it contains elements?
I tried with IsArray(myArray) function that give me always True,
otherwise if I try with UBound(myArray) function, I get an error.
Any ideas? thanks in advance,
Max
After declaring the array, you have to initialize it:
Dim myArray()
ReDim myArray(-1)
Then such code will always work:
If UBound(myArray)<0 Then
'array is empty....
Else
'array not empty....
End If
Edit: as you can't initialize the array, here is longer way to check if it's empty or not:
Dim x, myCount
myCount = 0
If IsArray(myArray) Then
For Each x In myArray
myCount = myCount + 1
Next
End If
If myCount=0 Then
'array is empty....
Else
'array not empty....
End If
First some notes.
Using Dim A() is not so practical in VBScript, better use ReDim
A(n).
For example ReDim A(-1) is also empty array (no elements) but initialized.
And as the best way coders to talk is by examples...
Dim a(), b(0), c
c = Array(a, b)
ReDim d(-1)
WScript.Echo "Testing HasBound:"
WScript.Echo "a = " & HasBound(a) & ",", _
"b = " & HasBound(b) & ",", _
"c = " & HasBound(c) & ",", _
"d = " & HasBound(d)
WScript.Echo "Testing HasItems:"
WScript.Echo "a = " & HasItems(a) & ",", _
"b = " & HasItems(b) & ",", _
"c = " & HasItems(c) & ",", _
"d = " & HasItems(d)
'> Testing HasBound:
'> a = False, b = True, c = True, d = True
'> Testing HasItems:
'> a = False, b = True, c = True, d = False
Function HasBound(anyArray)
On Error Resume Next
HasBound = UBound(anyArray)
HasBound = (0 = Err)
On Error Goto 0
End Function
Function HasItems(anyArray)
For Each HasItems In anyArray
HasItems = 1
Exit For
Next
HasItems = (HasItems > 0)
End Function
As you see, 2 functions with different purpose. The difference is visible on array d which "has-boundary" but "has-not-items".
I found a solution, I wrote a specific function to check if an array is null or not; the function doesn't check if it has elements inside but only if the array is declared as dynamic without dimensions and no elements.
Dim dynamic_array() 'array without a dimension
Dim empty_array(0) 'array with a dimension but without an element inside
Dim full_array(0) : full_array(0) = "max" 'array with a dimension and with an element inside
Function IsNullArray(input_array)
On Error Resume Next
Dim is_null : is_null = UBound(input_array)
If Err.Number = 0 Then
is_null = False
Else
is_null = True
End If
IsNullArray = is_null
End Function
If IsNullArray(dynamic_array) Then
Response.Write("<p>dynamic array not 'ReDimed'</p>")
End If
If Not IsNullArray(empty_array) Then
Response.Write("<p>" & UBound(empty_array) & "</p>") 'return the last index of the array
End If
If Not IsNullArray(full_array) Then
Response.Write("<p>" & full_array(UBound(full_array)) & "</p>") 'return the value of the last element of the array
End If
The one thing I can think of right now is:
On Error resume next
if UBound(myArray) < 0 then response.write "Empty array" end if
EDIT: Max's comment
I've always checked for UBound = 0 and the first element is empty too:
If UBound(myArray) = 0 Then
if myArray(0) = "" then ''Depending on the type of the array
''array is empty....
End If
End If