Evaluate Excel VBA boolean condition (not a formula) - vba

I have text in a cell A1 as below
((True And False Or True) And (True And (Not True))) And (False Or True)
I need to evaluate this text and put Boolean result (True / False) in another cell B1 using VBA code.
I tried to use Evaluate function, it is not working.
When I read this cell, it always return string type with double quote enclose in both side. Hence it is treated as string and not evaluating the Boolean expression.
"((True And True Or True) And (True And (True))) And (True Or True)"
I want to write this way, but it is not working
If Range("A1").Value = True Then
Range("B1").Value = True
Else
Range("B1").Value = False
End If
I tried to store in Boolean variable also
Dim Result as Boolean
Result = CBool(Range("A1").Value)
as it is string, I am getting type mismatch when I tried to convert using CBool.

You could try something like this, taking advantage of the Eval function in Access.
Public Function EvaluateExpression(Value As String) As Boolean
With CreateObject("Access.Application")
EvaluateExpression = .Eval(Value)
End With
End Function
Public Sub T()
Debug.Print EvaluateExpression("((True And True Or True) And (True And (True))) And (True Or True)")
End Sub
'True

Further to the accepted answer for this question you can use this code:
Option Explicit
Sub Test()
Debug.Print VBABooleanEvaluateOnTheFly("((True And False Or True) And (True And (Not True))) And (False Or True)")
Debug.Print VBABooleanEvaluateOnTheFly("((True And True Or True) And (True And (True))) And (True Or True)")
Debug.Print VBABooleanEvaluateOnTheFly("True")
Debug.Print VBABooleanEvaluateOnTheFly("False")
Debug.Print VBABooleanEvaluateOnTheFly("False Or True")
End Sub
Function VBABooleanEvaluateOnTheFly(strExpression As String) As Boolean
Dim blnResult As Boolean
Dim objVBComponent As Object
Set objVBComponent = ThisWorkbook.VBProject.VBComponents.Add(1)
With objVBComponent
.CodeModule.AddFromString "Function foo() As Boolean: foo = " & strExpression & ": End Function"
If Application.Run(.Name & ".foo") Then
blnResult = True
Else
blnResult = False
End If
End With
ThisWorkbook.VBProject.VBComponents.Remove objVBComponent
VBABooleanEvaluateOnTheFly = blnResult
End Function
You will need to tick the Trust access to the VBA project object model checkbox in the Trust Center settings.
Just to note a couple of things with this technique:
it is slow
there are likely a lot of ways it will break other things
it is vulnerable to code injection by a malicious user e.g. they may enter something like Sheet1.Cells.Delete instead of (True And False etc)

This is what you can do with VBA .Evaluate:
Option Explicit
Public Sub TestMe()
Dim cell01 As Range
Dim cell02 As Range
Set cell01 = Range("A1")
Set cell02 = Range("A2")
Range("A1") = "1+2+3+4+5"
Range("A2") = "TRUE and FALSE"
Debug.Print Evaluate(CStr(cell01))
'Debug.Print CBool(cell02) - this will be an error!
Debug.Print Evaluate(CBool("True") And CBool("False"))
Debug.Print Evaluate("=AND(TRUE,FALSE)")
Debug.Print Evaluate("=AND(TRUE,TRUE)")
Debug.Print Evaluate("=OR(TRUE,TRUE)")
End Sub
If you want to parse the TRUE and FALSE thing (commented in my answer), try to build a formula out of it and to evaluate it.
E.g., TRUE AND FALSE, should be translated to =AND(TRUE,FALSE). This gets evaluated easily by VBA as it is an Excel Formula. The translation is not a trivial task, but an interesting one.

Related

Passing Values in VBA

In the code I am posting, I am using a check box called "ACDS Test" and whenever it is checked it creates a sheet, then when it becomes unchecked it calls the upper function and deletes the sheet.
I am trying to add a message box that essentially works like a fail safe to ensure they want to delete the page. If they say they do not want to delete the page then I want the checkbox to stay checked.
For some reason I am getting this error message when I try to pass the value to make sure the checkbox stays checked and I cannot figure out why.
The error comes up on the line:
Sub ACDSTest_Click(CorrectValue As Integer)
And the specific error is: "Compile error: Procedure Declaration does not match description of event or procedure having the same name".
Any help is much appreciated! IF any more clarification is needed please feel free to ask!
Sub DeleteWorksheet(NameSheet As String)
Dim Ans As Long
Dim t As String
Dim CorrectValue As Integer
Dim i As Long, k As Long
k = Sheets.Count
Ans = MsgBox("Would you like to take this test off of the form?", vbYesNo)
Select Case Ans
Case vbYes
'Code reads through each page and finds one with corresponding name to string t
'Once it finds the correct page, it deletes it
For i = k To 1 Step -1
t = Sheets(i).Name
If t = NameSheet Then
Sheets(i).Delete
End If
Next i
CorrectValue = 0
Case vbNo
CorrectValue = 1
End Select
End Sub
Sub ACDSTest_Click(CorrectValue As Integer)
Dim NameSheet As String
Dim NameValue As String
NameSheet = "ACDS"
NameValue = "ACDS Test"
If ACDSTest.Value = True Then
CreateWorksheet (NameSheet), (NameValue)
Worksheets("Sheet1").Activate
Else
DeleteWorksheet (NameSheet)
If CorrectValue = 1 Then
ActiveSheet.Shapes("ACDS Test").ControlFormat.Value = 1
End If
End If
End Sub
The issue here is that the CorrectValue variable as you define it in DeleteWorksheet does not exist in the context of the
variable does not exist in context of the ACDSTest_Click subroutine. This is because variables defined within subroutines or functions are local to those functions. To correct this I would convert DeleteWorksheet to a function such as the below.
Further, the event that fires Private Sub ACDSTest_Click() cannot handle passing a value to that function, so changing it to Sub ACDSTest_Click(CorrectValue As Integer) causes an error.
Function DeleteWorksheet(ByVal SheetName As String) As Boolean
On Error GoTo SheetDNE
SheetName = Sheets(SheetName).Name 'Check if sheet exists w/o other objects
On Error GoTo 0
Select Case MsgBox("Would you like to take this test off of the form?", vbYesNo)
Case vbYes
Application.DisplayAlerts = False
Sheets(SheetName).Delete
Application.DisplayAlerts = True
DeleteWorksheet = True
Case Else: DeleteWorksheet = False
End Select
Exit Function 'Exit The Function w/o error
SheetDNE: 'Sheet Does Not Exist
MsgBox "The indicated sheet, " & SheetName & ", does not exist", vbOKOnly
End Function
And
Private Sub ACDSTest_Click()
Dim NameSheet As String
Dim NameValue As String
NameSheet = "ACDS"
NameValue = "ACDS Test"
If ACDSTest.Value = True Then
CreateWorksheet (NameSheet), (NameValue)
Worksheets("Sheet1").Activate
Else
If Not DeleteWorksheet(NameSheet) Then _
ActiveSheet.Shapes("ACDS Test").ControlFormat.Value = 1
End If
End Sub

VBA starts with or ends with a special character

I want to see if a string starts with or ends with a special character
testString("#Testing") Returns: true
testString("Testing\") Returns: true
testString("#Testing)") Returns: true
testString("Tes#ting~") Returns: true
testString("Tes#ting") Returns: false
testString("Testing") Returns: false
The idea is to use a regular expression
Dim rg As Variant
Set rg = CreateObject("VBScript.RegExp")
rg.Pattern = ""
returnFunc = rg.test(paramString)
However, I am not sure how to create a regular expression to check symbols.
All alternative solutions are welcome
So if it starts or ends with anything other than [a-Z][0-9]
Function test(x)
Dim rg As Variant
Set rg = CreateObject("VBScript.RegExp")
rg.Pattern = "^([^A-Za-z0-9].*|.*[^A-Za-z0-9])$"
test = rg.test(x)
End Function
Sub hoi()
Debug.Print test("#Testing")
Debug.Print test("Testing\")
Debug.Print test("#Testing)")
Debug.Print test("Tes#ting~")
Debug.Print test("Tes#ting")
Debug.Print test("Testing")
End Sub
If you don’t need to change your definition of special characters for different languages or other reasons then you can simply checking the first and last character against a list of valid characters would work.
Public Function testString(text As String)
testString = isCharAlphaNumeric(Left(text, 1)) Or isCharAlphaNumeric(Right(text, 1))
End Function
Public Function isCharAlphaNumeric(char)
Const valid As String = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
isCharAlphaNumeric = InStr(valid, char) = 0
End Function
Public Sub test()
Debug.Print testString("#Testing") ' Returns: true
Debug.Print testString("Testing\") ' Returns: true
Debug.Print testString("#Testing)") ' Returns: true
Debug.Print testString("Tes#ting~") ' Returns: true
Debug.Print testString("Tes#ting") ' Returns: false
Debug.Print testString("Testing") ' Returns: false
End Sub
To check if string does not start and end with alphanumeric characters using the VB Like operator:
If Not "#Testing" Like "[0-9A-Za-z]*[0-9A-Za-z]" Then MsgBox True
If the string might be less than 2 characters:
If string Like "[!0-9A-Za-z]*" Or string Like "*[!0-9A-Za-z]" Then MsgBox True

Userform Textboxs are numeric (and null)

I am implementing a Userform and wish to include some checks on the input data prior to running the Userform. In particular, check all inputs into the Userform textboxs are numerical, although it is valid a textbox is blank or Null. I have tried implementing the following:
Select Case KeyAscii
Case 0, 46, 48 To 57
Case Else
MsgBox "Only numbers allowed"
End Select
But this does not work.
Please, ideas?
Thank you very much!!!!!!!!!
Maybe bit long winded - I usually use a class module and the tag property on the control to decide what can be entered in a textbox.
Create a form with four text boxes.
Give the text boxes these tags:
1;CDBL
2;CINT
3;CSTR
4;CSENTENCE
The numbers are the columns to paste the values into when the form is saved (I haven't described that bit here).
The text describes what can be entered in the textbox - CDBL is numeric with 2 decimal places, CINT is numeric with 0 decimal places, CSTR is for Proper text and CSENTENCE is for sentence text.
Create a class module called clsControlText.
Add this code to the class module:
Public WithEvents txtBox As MSForms.TextBox
Private Sub txtBox_Change()
Static LastText As String
Static SecondTime As Boolean
Const MaxDecimal As Integer = 2
Const MaxWhole As Integer = 1
With txtBox
If InStr(.Tag, ";") > 0 Then
Select Case Split(.Tag, ";")(1)
Case "CDBL", "CCur"
'Allow only numbers with <=2 decimal places
If Not SecondTime Then
If .Text Like "[!0-9.-]*" Or Val(.Text) < -1 Or _
.Text Like "*.*.*" Or .Text Like "*." & String$(1 + MaxDecimal, "#") Or _
.Text Like "?*[!0-9.]*" Then
Beep
SecondTime = True
.Text = LastText
Else
LastText = .Text
End If
End If
SecondTime = False
Case "CINT"
'Allow only whole numbers.
If .Text Like "[!0-9]" Or Val(.Text) < -1 Or .Text Like "?*[!0-9]*" Then
Beep
.Text = LastText
Else
LastText = .Text
End If
Case "CSTR"
'Convert text to proper case.
.Text = StrConv(.Text, vbProperCase)
Case "CSENTENCE"
'Convert text to sentence case (capital after full-stop).
.Text = ProperCaps(.Text)
Case Else
'Allow anything.
End Select
End If
End With
End Sub
Private Function ProperCaps(strIn As String) As String
Dim objRegex As Object
Dim objRegMC As Object
Dim objRegM As Object
Set objRegex = CreateObject("vbscript.regexp")
strIn = LCase$(strIn)
With objRegex
.Global = True
.ignoreCase = True
.Pattern = "(^|[\.\?\!\r\t]\s?)([a-z])"
If .Test(strIn) Then
Set objRegMC = .Execute(strIn)
For Each objRegM In objRegMC
Mid$(strIn, objRegM.firstindex + 1, objRegM.Length) = UCase$(objRegM)
Next
End If
ProperCaps = strIn
End With
End Function
Add this code to the user form:
Private colTextBoxes As Collection
Private Sub UserForm_Initialize()
Dim ctrlSelect As clsControlText
Dim ctrl As Control
Me.Caption = ThisWorkbook.Name
Set colTextBoxes = New Collection
For Each ctrl In Me.Controls
Select Case TypeName(ctrl)
Case "TextBox"
Set ctrlSelect = New clsControlText
Set ctrlSelect.txtBox = ctrl
colTextBoxes.Add ctrlSelect
End Select
Next ctrl
End Sub
NB: Not all this code is mine. I found ProperCaps and the code for CDBL elsewhere on this site - or maybe MrExcel.
You could use a basic LIKE or Regexp
Sub Test()
Debug.Print StrCheck("")
Debug.Print StrCheck("hello kitty")
Debug.Print StrCheck("4156")
End Sub
function
Function StrCheck(strIn As String) As String
Dim objRegex As Object
Set objRegex = CreateObject("vbscript.regexp")
objRegex.Pattern = "\d+"
'vaidate empty string
If Len(Trim(strIn)) = 0 Then
StrCheck = True
Else
'validate whether non-empty string is numeric
StrCheck = objRegex.Test(strIn)
End If
End Function

Determining whether an object is a member of a collection in VBA

How do I determine whether an object is a member of a collection in VBA?
Specifically, I need to find out whether a table definition is a member of the TableDefs collection.
Isn't it good enough?
Public Function Contains(col As Collection, key As Variant) As Boolean
Dim obj As Variant
On Error GoTo err
Contains = True
obj = col(key)
Exit Function
err:
Contains = False
End Function
Not exactly elegant, but the best (and quickest) solution i could find was using OnError. This will be significantly faster than iteration for any medium to large collection.
Public Function InCollection(col As Collection, key As String) As Boolean
Dim var As Variant
Dim errNumber As Long
InCollection = False
Set var = Nothing
Err.Clear
On Error Resume Next
var = col.Item(key)
errNumber = CLng(Err.Number)
On Error GoTo 0
'5 is not in, 0 and 438 represent incollection
If errNumber = 5 Then ' it is 5 if not in collection
InCollection = False
Else
InCollection = True
End If
End Function
Your best bet is to iterate over the members of the collection and see if any match what you are looking for. Trust me I have had to do this many times.
The second solution (which is much worse) is to catch the "Item not in collection" error and then set a flag to say the item does not exist.
This is an old question. I have carefully reviewed all the answers and comments, tested the solutions for performance.
I came up with the fastest option for my environment which does not fail when a collection has objects as well as primitives.
Public Function ExistsInCollection(col As Collection, key As Variant) As Boolean
On Error GoTo err
ExistsInCollection = True
IsObject(col.item(key))
Exit Function
err:
ExistsInCollection = False
End Function
In addition, this solution does not depend on hard-coded error values. So the parameter col As Collection can be substituted by some other collection type variable, and the function must still work. E.g., on my current project, I will have it as col As ListColumns.
You can shorten the suggested code for this as well as generalize for unexpected errors.
Here you go:
Public Function InCollection(col As Collection, key As String) As Boolean
On Error GoTo incol
col.Item key
incol:
InCollection = (Err.Number = 0)
End Function
In your specific case (TableDefs) iterating over the collection and checking the Name is a good approach. This is OK because the key for the collection (Name) is a property of the class in the collection.
But in the general case of VBA collections, the key will not necessarily be part of the object in the collection (e.g. you could be using a Collection as a dictionary, with a key that has nothing to do with the object in the collection). In this case, you have no choice but to try accessing the item and catching the error.
I created this solution from the above suggestions mixed with microsofts solution of for iterating through a collection.
Public Function InCollection(col As Collection, Optional vItem, Optional vKey) As Boolean
On Error Resume Next
Dim vColItem As Variant
InCollection = False
If Not IsMissing(vKey) Then
col.item vKey
'5 if not in collection, it is 91 if no collection exists
If Err.Number <> 5 And Err.Number <> 91 Then
InCollection = True
End If
ElseIf Not IsMissing(vItem) Then
For Each vColItem In col
If vColItem = vItem Then
InCollection = True
GoTo Exit_Proc
End If
Next vColItem
End If
Exit_Proc:
Exit Function
Err_Handle:
Resume Exit_Proc
End Function
I have some edit, best working for collections:
Public Function Contains(col As collection, key As Variant) As Boolean
Dim obj As Object
On Error GoTo err
Contains = True
Set obj = col.Item(key)
Exit Function
err:
Contains = False
End Function
For the case when key is unused for collection:
Public Function Contains(col As Collection, thisItem As Variant) As Boolean
Dim item As Variant
Contains = False
For Each item In col
If item = thisItem Then
Contains = True
Exit Function
End If
Next
End Function
this version works for primitive types and for classes (short test-method included)
' TODO: change this to the name of your module
Private Const sMODULE As String = "MVbaUtils"
Public Function ExistsInCollection(oCollection As Collection, sKey As String) As Boolean
Const scSOURCE As String = "ExistsInCollection"
Dim lErrNumber As Long
Dim sErrDescription As String
lErrNumber = 0
sErrDescription = "unknown error occurred"
Err.Clear
On Error Resume Next
' note: just access the item - no need to assign it to a dummy value
' and this would not be so easy, because we would need different
' code depending on the type of object
' e.g.
' Dim vItem as Variant
' If VarType(oCollection.Item(sKey)) = vbObject Then
' Set vItem = oCollection.Item(sKey)
' Else
' vItem = oCollection.Item(sKey)
' End If
oCollection.Item sKey
lErrNumber = CLng(Err.Number)
sErrDescription = Err.Description
On Error GoTo 0
If lErrNumber = 5 Then ' 5 = not in collection
ExistsInCollection = False
ElseIf (lErrNumber = 0) Then
ExistsInCollection = True
Else
' Re-raise error
Err.Raise lErrNumber, mscMODULE & ":" & scSOURCE, sErrDescription
End If
End Function
Private Sub Test_ExistsInCollection()
Dim asTest As New Collection
Debug.Assert Not ExistsInCollection(asTest, "")
Debug.Assert Not ExistsInCollection(asTest, "xx")
asTest.Add "item1", "key1"
asTest.Add "item2", "key2"
asTest.Add New Collection, "key3"
asTest.Add Nothing, "key4"
Debug.Assert ExistsInCollection(asTest, "key1")
Debug.Assert ExistsInCollection(asTest, "key2")
Debug.Assert ExistsInCollection(asTest, "key3")
Debug.Assert ExistsInCollection(asTest, "key4")
Debug.Assert Not ExistsInCollection(asTest, "abcx")
Debug.Print "ExistsInCollection is okay"
End Sub
It requires some additional adjustments in case the items in the collection are not Objects, but Arrays. Other than that it worked fine for me.
Public Function CheckExists(vntIndexKey As Variant) As Boolean
On Error Resume Next
Dim cObj As Object
' just get the object
Set cObj = mCol(vntIndexKey)
' here's the key! Trap the Error Code
' when the error code is 5 then the Object is Not Exists
CheckExists = (Err <> 5)
' just to clear the error
If Err <> 0 Then Call Err.Clear
Set cObj = Nothing
End Function
Source: http://coderstalk.blogspot.com/2007/09/visual-basic-programming-how-to-check.html
It works for me
Public Function contains(col As Collection, key As Variant) As Boolean
For Each element In col
If (element = key) Then
contains = True
Exit Function
End If
Next
contains = False
End Function
Not my code, but I think it's pretty nicely written. It allows to check by the key as well as by the Object element itself and handles both the On Error method and iterating through all Collection elements.
https://danwagner.co/how-to-check-if-a-collection-contains-an-object/
I'll not copy the full explanation since it is available on the linked page. Solution itself copied in case the page eventually becomes unavailable in the future.
The doubt I have about the code is the overusage of GoTo in the first If block but that's easy to fix for anyone so I'm leaving the original code as it is.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'INPUT : Kollection, the collection we would like to examine
' : (Optional) Key, the Key we want to find in the collection
' : (Optional) Item, the Item we want to find in the collection
'OUTPUT : True if Key or Item is found, False if not
'SPECIAL CASE: If both Key and Item are missing, return False
Option Explicit
Public Function CollectionContains(Kollection As Collection, Optional Key As Variant, Optional Item As Variant) As Boolean
Dim strKey As String
Dim var As Variant
'First, investigate assuming a Key was provided
If Not IsMissing(Key) Then
strKey = CStr(Key)
'Handling errors is the strategy here
On Error Resume Next
CollectionContains = True
var = Kollection(strKey) '<~ this is where our (potential) error will occur
If Err.Number = 91 Then GoTo CheckForObject
If Err.Number = 5 Then GoTo NotFound
On Error GoTo 0
Exit Function
CheckForObject:
If IsObject(Kollection(strKey)) Then
CollectionContains = True
On Error GoTo 0
Exit Function
End If
NotFound:
CollectionContains = False
On Error GoTo 0
Exit Function
'If the Item was provided but the Key was not, then...
ElseIf Not IsMissing(Item) Then
CollectionContains = False '<~ assume that we will not find the item
'We have to loop through the collection and check each item against the passed-in Item
For Each var In Kollection
If var = Item Then
CollectionContains = True
Exit Function
End If
Next var
'Otherwise, no Key OR Item was provided, so we default to False
Else
CollectionContains = False
End If
End Function
i used this code to convert array to collection and back to array to remove duplicates, assembled from various posts here (sorry for not giving properly credit).
Function ArrayRemoveDups(MyArray As Variant) As Variant
Dim nFirst As Long, nLast As Long, i As Long
Dim item As Variant, outputArray() As Variant
Dim Coll As New Collection
'Get First and Last Array Positions
nFirst = LBound(MyArray)
nLast = UBound(MyArray)
ReDim arrTemp(nFirst To nLast)
i = nFirst
'convert to collection
For Each item In MyArray
skipitem = False
For Each key In Coll
If key = item Then skipitem = True
Next
If skipitem = False Then Coll.Add (item)
Next item
'convert back to array
ReDim outputArray(0 To Coll.Count - 1)
For i = 1 To Coll.Count
outputArray(i - 1) = Coll.item(i)
Next
ArrayRemoveDups = outputArray
End Function
I did it like this, a variation on Vadims code but to me a bit more readable:
' Returns TRUE if item is already contained in collection, otherwise FALSE
Public Function Contains(col As Collection, item As String) As Boolean
Dim i As Integer
For i = 1 To col.Count
If col.item(i) = item Then
Contains = True
Exit Function
End If
Next i
Contains = False
End Function
I wrote this code. I guess it can help someone...
Public Function VerifyCollection()
For i = 1 To 10 Step 1
MyKey = "A"
On Error GoTo KillError:
Dispersao.Add 1, MyKey
GoTo KeepInForLoop
KillError: 'If My collection already has the key A Then...
count = Dispersao(MyKey)
Dispersao.Remove (MyKey)
Dispersao.Add count + 1, MyKey 'Increase the amount in relationship with my Key
count = Dispersao(MyKey) 'count = new amount
On Error GoTo -1
KeepInForLoop:
Next
End Function

How to check for empty array in vba macro [duplicate]

This question already has answers here:
How do I determine if an array is initialized in VB6?
(24 answers)
Closed 3 years ago.
I want to check for empty arrays. Google gave me varied solutions but nothing worked. Maybe I am not applying them correctly.
Function GetBoiler(ByVal sFile As String) As String
'Email Signature
Dim fso As Object
Dim ts As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.GetFile(sFile).OpenAsTextStream(1, -2)
GetBoiler = ts.ReadAll
ts.Close
End Function
Dim FileNamesList As Variant, i As Integer
' activate the desired startfolder for the filesearch
FileNamesList = CreateFileList("*.*", False) ' Returns File names
' performs the filesearch, includes any subfolders
' present the result
' If there are Signatures then populate SigString
Range("A:A").ClearContents
For i = 1 To UBound(FileNamesList)
Cells(i + 1, 1).Formula = FileNamesList(i)
Next i
SigString = FileNamesList(3)
If Dir(SigString) <> "" Then
Signature = GetBoiler(SigString)
Else
Signature = ""
End If
Here if FileNamesList array is empty, GetBoiler(SigString) should not get called at all. When FileNamesList array is empty, SigString is also empty and this calls GetBoiler() function with empty string. I get an error at line
Set ts = fso.GetFile(sFile).OpenAsTextStream(1, -2)
since sFile is empty. Any way to avoid that?
Go with a triple negative:
If (Not Not FileNamesList) <> 0 Then
' Array has been initialized, so you're good to go.
Else
' Array has NOT been initialized
End If
Or just:
If (Not FileNamesList) = -1 Then
' Array has NOT been initialized
Else
' Array has been initialized, so you're good to go.
End If
In VB, for whatever reason, Not myArray returns the SafeArray pointer. For uninitialized arrays, this returns -1. You can Not this to XOR it with -1, thus returning zero, if you prefer.
(Not myArray) (Not Not myArray)
Uninitialized -1 0
Initialized -someBigNumber someOtherBigNumber
Source
As you are dealing with a string array, have you considered Join?
If Len(Join(FileNamesList)) > 0 Then
If you test on an array function it'll work for all bounds:
Function IsVarArrayEmpty(anArray As Variant)
Dim i As Integer
On Error Resume Next
i = UBound(anArray,1)
If Err.number = 0 Then
IsVarArrayEmpty = False
Else
IsVarArrayEmpty = True
End If
End Function
I see similar answers on here... but not mine...
This is how I am unfortunatley going to deal with it... I like the len(join(arr)) > 0 approach, but it wouldn't work if the array was an array of emptystrings...
Public Function arrayLength(arr As Variant) As Long
On Error GoTo handler
Dim lngLower As Long
Dim lngUpper As Long
lngLower = LBound(arr)
lngUpper = UBound(arr)
arrayLength = (lngUpper - lngLower) + 1
Exit Function
handler:
arrayLength = 0 'error occured. must be zero length
End Function
When writing VBA there is this sentence in my head: "Could be so easy, but..."
Here is what I adopted it to:
Private Function IsArrayEmpty(arr As Variant)
' This function returns true if array is empty
Dim l As Long
On Error Resume Next
l = Len(Join(arr))
If l = 0 Then
IsArrayEmpty = True
Else
IsArrayEmpty = False
End If
If Err.Number > 0 Then
IsArrayEmpty = True
End If
On Error GoTo 0
End Function
Private Sub IsArrayEmptyTest()
Dim a As Variant
a = Array()
Debug.Print "Array is Empty is " & IsArrayEmpty(a)
If IsArrayEmpty(a) = False Then
Debug.Print " " & Join(a)
End If
End Sub
This code doesn't do what you expect:
If Dir(SigString) <> "" Then
Signature = GetBoiler(SigString)
Else
Signature = ""
End If
If you pass an empty string ("") or vbNullString to Dir, it will return the name of the first file in the current directory path (the path returned by CurDir$). So, if SigString is empty, your If condition will evaluate to True because Dir will return a non-empty string (the name of the first file in the current directory), and GetBoiler will be called. And if SigString is empty, the call to fso.GetFile will fail.
You should either change your condition to check that SigString isn't empty, or use the FileSystemObject.FileExists method instead of Dir for checking if the file exists. Dir is tricky to use precisely because it does things you might not expect it to do. Personally, I would use Scripting.FileSystemObject over Dir because there's no funny business (FileExists returns True if the file exists, and, well, False if it doesn't). What's more, FileExists expresses the intent of your code much clearly than Dir.
Method 1: Check that SigString is non-empty first
If SigString <> "" And Dir(SigString) <> "" Then
Signature = GetBoiler(SigString)
Else
Signature = ""
End If
Method 2: Use the FileSystemObject.FileExists method
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists(SigString) Then
Signature = GetBoiler(SigString)
Else
Signature = ""
End If
I am simply pasting below the code by the great Chip Pearson. It works a charm.
Here's his page on array functions.
I hope this helps.
Public Function IsArrayEmpty(Arr As Variant) As Boolean
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' IsArrayEmpty
' This function tests whether the array is empty (unallocated). Returns TRUE or FALSE.
'
' The VBA IsArray function indicates whether a variable is an array, but it does not
' distinguish between allocated and unallocated arrays. It will return TRUE for both
' allocated and unallocated arrays. This function tests whether the array has actually
' been allocated.
'
' This function is really the reverse of IsArrayAllocated.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim LB As Long
Dim UB As Long
err.Clear
On Error Resume Next
If IsArray(Arr) = False Then
' we weren't passed an array, return True
IsArrayEmpty = True
End If
' Attempt to get the UBound of the array. If the array is
' unallocated, an error will occur.
UB = UBound(Arr, 1)
If (err.Number <> 0) Then
IsArrayEmpty = True
Else
''''''''''''''''''''''''''''''''''''''''''
' On rare occasion, under circumstances I
' cannot reliably replicate, Err.Number
' will be 0 for an unallocated, empty array.
' On these occasions, LBound is 0 and
' UBound is -1.
' To accommodate the weird behavior, test to
' see if LB > UB. If so, the array is not
' allocated.
''''''''''''''''''''''''''''''''''''''''''
err.Clear
LB = LBound(Arr)
If LB > UB Then
IsArrayEmpty = True
Else
IsArrayEmpty = False
End If
End If
End Function
Simplified check for Empty Array:
Dim exampleArray() As Variant 'Any Type
If ((Not Not exampleArray) = 0) Then
'Array is Empty
Else
'Array is Not Empty
End If
Here is another way to do it. I have used it in some cases and it's working.
Function IsArrayEmpty(arr As Variant) As Boolean
Dim index As Integer
index = -1
On Error Resume Next
index = UBound(arr)
On Error GoTo 0
If (index = -1) Then IsArrayEmpty = True Else IsArrayEmpty = False
End Function
Based on ahuth's answer;
Function AryLen(ary() As Variant, Optional idx_dim As Long = 1) As Long
If (Not ary) = -1 Then
AryLen = 0
Else
AryLen = UBound(ary, idx_dim) - LBound(ary, idx_dim) + 1
End If
End Function
Check for an empty array; is_empty = AryLen(some_array)=0
Public Function IsEmptyArray(InputArray As Variant) As Boolean
On Error GoTo ErrHandler:
IsEmptyArray = Not (UBound(InputArray) >= 0)
Exit Function
ErrHandler:
IsEmptyArray = True
End Function
You can use the below function to check if variant or string array is empty in vba
Function IsArrayAllocated(Arr As Variant) As Boolean
On Error Resume Next
IsArrayAllocated = IsArray(Arr) And _
Not IsError(LBound(Arr, 1)) And _
LBound(Arr, 1) <= UBound(Arr, 1)
End Function
Sample usage
Public Function test()
Dim Arr(1) As String
Arr(0) = "d"
Dim x As Boolean
x = IsArrayAllocated(Arr)
End Function
Another method would be to do it sooner. You can create a Boolean variable and set it to true once you load data to the array. so all you really need is a simple if statement of when you load data into the array.
To check whether a Byte array is empty, the simplest way is to use the VBA function StrPtr().
If the Byte array is empty, StrPtr() returns 0; otherwise, it returns a non-zero value (however, it's not the address to the first element).
Dim ar() As Byte
Debug.Assert StrPtr(ar) = 0
ReDim ar(0 to 3) As Byte
Debug.Assert StrPtr(ar) <> 0
However, it only works with Byte array.
Function IsVarArrayEmpty(anArray As Variant) as boolean
On Error Resume Next
IsVarArrayEmpty = true
IsVarArrayEmpty = UBound(anArray) < LBound(anArray)
End Function
Maybe ubound crashes and it remains at true, and if ubound < lbound, it's empty
I'll generalize the problem and the Question as intended.
Test assingment on the array, and catch the eventual error
Function IsVarArrayEmpty(anArray as Variant)
Dim aVar as Variant
IsVarArrayEmpty=False
On error resume next
aVar=anArray(1)
If Err.number then '...still, it might not start at this index
aVar=anArray(0)
If Err.number then IsVarArrayEmpty=True ' neither 0 or 1 yields good assignment
EndIF
End Function
Sure it misses arrays with all negative indexes or all > 1... is that likely? in weirdland, yes.
Personally, I think one of the answers above can be modified to check if the array has contents:
if UBound(ar) > LBound(ar) Then
This handles negative number references and takes less time than some of the other options.
You can check if the array is empty by retrieving total elements count using JScript's VBArray() object (works with arrays of variant type, single or multidimensional):
Sub Test()
Dim a() As Variant
Dim b As Variant
Dim c As Long
' Uninitialized array of variant
' MsgBox UBound(a) ' gives 'Subscript out of range' error
MsgBox GetElementsCount(a) ' 0
' Variant containing an empty array
b = Array()
MsgBox GetElementsCount(b) ' 0
' Any other types, eg Long or not Variant type arrays
MsgBox GetElementsCount(c) ' -1
End Sub
Function GetElementsCount(aSample) As Long
Static oHtmlfile As Object ' instantiate once
If oHtmlfile Is Nothing Then
Set oHtmlfile = CreateObject("htmlfile")
oHtmlfile.parentWindow.execScript ("function arrlength(arr) {try {return (new VBArray(arr)).toArray().length} catch(e) {return -1}}"), "jscript"
End If
GetElementsCount = oHtmlfile.parentWindow.arrlength(aSample)
End Function
For me it takes about 0.3 mksec for each element + 15 msec initialization, so the array of 10M elements takes about 3 sec. The same functionality could be implemented via ScriptControl ActiveX (it is not available in 64-bit MS Office versions, so you can use workaround like this).
if Ubound(yourArray)>-1 then
debug.print "The array is not empty"
else
debug.print "EMPTY"
end if
You can check its count.
Here cid is an array.
if (jsonObject("result")("cid").Count) = 0 them
MsgBox "Empty Array"
I hope this helps.
Have a nice day!
Another solution to test for empty array
if UBound(ar) < LBound(ar) then msgbox "Your array is empty!"
Or, if you already know that LBound is 0
if -1 = UBound(ar) then msgbox "Your array is empty!"
This may be faster than join(). (And I didn't check with negative indexes)
Here is my sample to filter 2 string arrays so they do not share same strings.
' Filtering ar2 out of strings that exists in ar1
For i = 0 To UBound(ar1)
' filter out any ar2.string that exists in ar1
ar2 = Filter(ar2 , ar1(i), False)
If UBound(ar2) < LBound(ar2) Then
MsgBox "All strings are the same.", vbExclamation, "Operation ignored":
Exit Sub
End If
Next
' At this point, we know that ar2 is not empty and it is filtered
'
Public Function arrayIsEmpty(arrayToCheck() As Variant) As Boolean
On Error GoTo Err:
Dim forCheck
forCheck = arrayToCheck(0)
arrayIsEmpty = False
Exit Function
Err:
arrayIsEmpty = True
End Function