convert Function so as to give 3rd state - vba

Would it be easy to convert the following function so that instead of just 0 or 1 it gave the following three outputs:
0 - means file closed
1 - means file is already open
2 - means file does not exist
Here's the Function
Function IsFileReadOnlyOpen(FileName As String)
Dim iFilenum As Long
Dim iErr As Long
On Error Resume Next
iFilenum = FreeFile()
Open FileName For Input Lock Read As #iFilenum
Close iFilenum
iErr = Err
On Error GoTo 0
Select Case iErr
Case 0: IsFileReadOnlyOpen = 0
Case 70: IsFileReadOnlyOpen = 1
Case Else: Error iErr
End Select
End Function

You could add this at the beginning of your function:
If Dir(FileName) = "" Then 'File does not exist
IsFileReadOnlyOpen = 2
Exit Function 'Or not - I don't know if you want to create the file or exit in that case.
End If
I agree with the comment that you should use enum to make it easier to understand.
PS: As commented by Martin Milan this might cause issues. Alternatively, you can use this:
With New FileSystemObject
If .FileExists(FileName) Then
IsFileReadOnlyOpen = 2
Exit Function 'Or not - I don't know if you want to create the file or exit in that case.
End If
End With

You can use the FileSystemObject to test for the existence of a file explicitly, if that is your difficulty.
You'll need to add a reference to the Microsoft Scripting Runtime library though in order to do that, and I tend to try to avoid that.
You can use FindFirstFile from the Win32API to test this, but that's a little more involved - and also won't help you if the user is actually running on a Mac...

have ended up with:
Enum FileOpenState
ExistsAndClosed = 0
ExistsAndOpen = 1
NotExists = 2
End Enum
Function IsFileReadOnlyOpen(FileName As String)
With New FileSystemObject
If Not .FileExists(FileName) Then
IsFileReadOnlyOpen = 2 ' NotExists = 2
Exit Function 'Or not - I don't know if you want to create the file or exit in that case.
End If
End With
Dim iFilenum As Long
Dim iErr As Long
On Error Resume Next
iFilenum = FreeFile()
Open FileName For Input Lock Read As #iFilenum
Close iFilenum
iErr = Err
On Error GoTo 0
Select Case iErr
Case 0: IsFileReadOnlyOpen = 0
Case 70: IsFileReadOnlyOpen = 1
Case Else: IsFileReadOnlyOpen = 1 'Error iErr
End Select
End Function

Related

Get statement generates: "Invalid use of Null"

I need to open (and close) multiple files. That's why I wrote a little Function FileOpen. The code works perfectly in my subroutines where I am using the byte array B but getting an error message in the function. I pass the full name "FN" of the file. The file exists. The ReDim works fine, but I get the
error 94 "Invalid use of null"
on the Get statement.
Sub main()
Dim FN As String: FN = "c:\tmp\test.docx"
Dim B() As Byte
If FileOpen(FN, B) < 0 Then Debug.Print " Error"
End Sub
Function FileOpen(FN, ByRef B) As Long
Dim nFile As Integer
nFile = FreeFile
Open FN For Binary Access Read As nFile
If LOF(nFile) > 0 Then
ReDim B(LOF(nFile) - 1)
Get nFile, , B
Close nFile
FileOpen = 0
Else
Close nFile
FileOpen = -1
End If
End Function
How can I correct it?

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

Excel macro for checking existence of file

I don't have any understanding of VBA. I have excel file which contains File path and I want to find existence of file in that location.
I tried the following but need something better than this
Sub Test_File_Exist_With_Dir()
Dim FilePath As String
Dim TestStr As String
FilePath = ActiveSheet.Range("A7").Value
TestStr = ""
On Error Resume Next
TestStr = Dir(FilePath)
On Error GoTo 0
If TestStr = "" Then
ActiveSheet.Range("B7").Value = 0
Else
ActiveSheet.Range("B7").Value = 1
End If
End Sub
Expected output
File path Existence
C:\Users\Desktop\Excel\Jan15.txt 1
C:\Users\Desktop\Excel\Feb15.txt 1
C:\Users\Desktop\Excel\Mar15.txt 1
C:\Users\Desktop\Excel\Apr15.txt 0
C:\Users\Desktop\Excel\May15.txt 0
If I add new row to data then its existence should automatically populate.
Yau can use this as a function directly in your workbook as a classic Excel formula, just type =File_Exist(A1) and this will work as a normal function (you can autofill next rows easily).
Public Function File_Exist(ByVal FilePath As String) As Integer
On Error Resume Next
Dim TestStr As String
TestStr = Dir(FilePath)
On Error GoTo 0
If TestStr <> "" Then
File_Exist = 1
Else
File_Exist = 0
End If
End Function
If you want to test existence automatically and populate every time you add new row to data then you'll have to use Worksheet_SelectionChange but it'll be more difficult than this and not so useful if you have a practical function!

VBA: If A found in line 1, and B found in line 2.. then

I currently have my VBA set up to read a text file (using FileSystemObject) and to find certain strings. This all works great. But what I am trying to achieve is for VBA to read through the text and when it finds a certain string (A) and in the next line below it another string (B) it will do something. But only if B is right after A.
Example:
Find in the following text "Bob's House" and in the next line after that "Electricity.
Text 1: - Return False
blablabla *Bob's House* blablabla
blablabla blablabla blablabla
blabla *Electiricity* blablabla
Text 1: - Return True
blablabla *Bob's House* blablabla
blabla *Electiricity* blablabla
This is what I have so far:
Set fsFile = fs.OpenTextFile(FilePath, 1, False)
sLine = fsFile.ReadLine
If VBA.InStr(1, sLine, "Bobs House") > 0 Then
checkpointHeading = True
End If
If VBA.InStr(1, sLine, "Electricity") > 0 Then
checkpointSubheading = True
End If
If checkpointHeading = True And checkpointSubheading = True Then
MsgBox "Found it!"
End If
This returns "Found it" regardless of how many lines there are between Bobs House and Electricity. Which makes sense. But how do I force the second restraint only after the first is found the line before?
Is there something like sLine +1 / .Readline + 1 (and then apply the second if statement inside the first?).
Thanks in advance, R
You are having this trouble because you are not resetting the 'Bob's House' variable on the next line if that line doesn't equal 'Electricity'. So once Bob's House is found it will always be true and it doesn't matter where 'Electricity' comes in.
You can accomplish what you are after one of two ways. Using Booleans like you have and the code in 'Way 1' (which I've bloated out a bit so its easy to follow), or probably a better way where you simply set the current line string variable to a new string variable which holds the previous line at the end of the loop and then check both of these variables next line like in 'Way 2'.
(Note there are a couple of typos in your example which I've retained so the code works with the example).
Sub Way1()
Dim fs As New FileSystemObject, fsfile As Object
Dim sLine As String
Dim checkpointHeading As Boolean, checkpointSubheading As Boolean
'Open file
Set fsfile = fs.OpenTextFile("G:Test.txt", 1, False)
'Loop through
Do While fsfile.AtEndOfStream <> True
sLine = fsfile.ReadLine
If VBA.InStr(1, sLine, "Bob's House") > 0 Then
checkpointHeading = True
Else
'If the line doesn't have Bob's House then check if the line before did
If checkpointHeading Then
'If it did then check for Electricity
If VBA.InStr(1, sLine, "Electiricity") > 0 Then
'If it's found then happy days
checkpointSubheading = True
Else
'If it's not found then reset everything
checkpointHeading = False: checkpointSubheading = False
End If
End If
End If
'Check if we've found it
If checkpointHeading = True And checkpointSubheading = True Then
MsgBox "Found it!"
'You may want to reset here to be safe
checkpointHeading = False: checkpointSubheading = False
End If
Loop
fsfile.Close
Set fsfile = Nothing
Set fs = Nothing
End Sub
The easier and more concise way 2:
Sub Way2()
Dim fs As New FileSystemObject, fsfile As Object
Dim sLine As String, sPrevLine As String
'Open file
Set fsfile = fs.OpenTextFile("G:Test.txt", 1, False)
'Loop through
Do While fsfile.AtEndOfStream <> True
sLine = fsfile.ReadLine
If VBA.Len(sPrevLine) > 0 Then
If VBA.InStr(1, sPrevLine, "Bob's House") > 0 And VBA.InStr(1, sLine, "Electiricity") Then
MsgBox "Found it!"
End If
End If
'Set the current line to the previous line *at the end of the loop*
sPrevLine = sLine
Loop
fsfile.Close
Set fsfile = Nothing
Set fs = Nothing
End Sub
I didn't test it, but this should demonstrate the logic:
Const filepath = "..."
Sub test()
Dim fs
Dim fsFile
Dim found As Boolean
Dim flag As Boolean
Dim sLine As String
Set fs = CreateObject("Scripting.FileSystemObject")
Set fsFile = fs.OpenTextFile(filepath, 1, False)
found = False
flag = False
Do While Not fsFile.AtEndOfStream And Not found
sLine = fsFile.readLine
If flag And InStr(sLine, "Electricity") Then found = True
flag = (InStr(sLine, "Bobs House") > 0)
Loop
If found Then
MsgBox sLine
Else
MsgBox "not found"
End If
End Sub
Edit: Tested.
Something like this:
sLine = fsFile.ReadLine
If isHeading Then
If InStr(1, sLine, "Electricity") > 0 Then
MsgBox "Found It!"
End If
isHeading = False
End If
If InStr(1, sLine, "Bobs House") > 0 Then
isHeading = True
End If

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