VBA Array Declaration Using a Variable - ms-access

Is it true that you cannot declare a visual basic array of a size provided by a variable? This seems like a reasonable requirement for any scripting language, and so I expect I am doing something wrong.
In the following example...
Sub TestRoutine()
Dim tVar As Integer: tVar = 5
Dim tArr(tVar) As String
tArr(3) = "SUCCESS"
MsgBox tArr(3)
End Sub
... the execution fails with the message Compile error: Constant expression required
I use a dynamic array instead, but this seems like an ugly workaround. Is there something I am missing here?

That is right. The closest you can get is - as the compiler suggests - to use a constant:
Sub TestRoutine()
Const tVar As Integer = 5
Dim tArr(tVar) As String
tArr(3) = "SUCCESS"
MsgBox tArr(3)
End Sub
Another option which is handy, say, when using Split or Array, is to use a Variant:
Sub TestRoutine()
Dim vArr As Variant
vArr = Array("0", "1", "2", "Yet a SUCCESS")
MsgBox vArr(3)
End Sub

Related

VBA - Compile error: Expected: If or Select or Sub or Function or Property or Type or With or Enum or end of statement

Forgive me for the noob question. Working in VBA is not my normal job.
I get the error listed in the title. The offending code is this:
While index <= 10
index = index + 1
End While
I can't imagine what could be wrong with that, seeing that it is copied directly from the documentation: https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/while-end-while-statement
Seeing that the code can't be the problem, I'm supposing the problem must be something in the context, but before I put this block in, it was working fine.
This block of code is part of a simple, private subroutine which declares a variable, assign a value to it, and then writes it into a file, like so:
Private Sub btnExpAll_Click()
Dim sFldr As String
sFldr = BrowseFolder("Where do you want to export the files?")
If sFldr = "" Then Exit Sub
DoCmd.Hourglass True
Dim sListLocal As String
Dim index As Integer
Dim strInputFileNameLocal As String
sListLocal = ""
index = 0
While index <= 10
index = index + 1
End While
strInputFileNameLocal = sFldr & "\list-local.html"
Open strInputFileNameLocal For Output As #1
Print #1, sListLocal
Close #1
DoCmd.Hourglass False
End Sub
The idea is to add more text to the file as we go through the while loop based on certain conditions - but since I can't even get the while loop to compile...
Link referenced in question is VB.net example, which has similarities to VBA and in some simple examples, easily confused. Replace End While with Wend or use Do … Loop structure.
Review: https://excelmacromastery.com/vba-while-loop/

Dlookup Hyperlinks to display in MsgBox

Im attempting to replace all my path constants in VBA with an administration table in my datatbase so users can change the location of folders and files without having to edit vba code.
the code is as follows
Private Sub Command8_Click()
Debug.Print DLookup("fsFileLink", "tblFileSystem", "fsFileName= 'TEMPLATES'")
MsgBox = DLookup("fsFileLink", "tblFileSystem", "fsFileName= 'TEMPLATES'")
End Sub
debug.print returns
C:\Users\... \templates\
but the msgbox returns the error "left must be variant or object".
How can I get my dlookup value as a string that I can display and edit in a text box?
Thank You
I'm using the below code:
Dim strCOCTemplate As String
strCOCTemplate = Nz(DLookup("fsFileLink", "tblFileSystem", "fsFileName= 'TEMPLATE_COC2'"), "none")
If strCOCTemplate = "none" Then Err.Raise Number:=11001, Description:=ERR_DESC_11001
Set docJobSpec = WordApp.Documents.Add(Template:=strCOCTemplate, NewTemplate:=True)
Dlookup returns a variant, but Template:= can't accept null values and throws an akward error. I compensated for this by using nz() function to return a string, and raised my own custom error to say the table couldn't find data

Returning Multiple Values from a Custom Function

So, I've got a ValidateForm function that loops through a form to validate each control. I have a collection set up called ValData to capture different bits of info to be passed out of the function. This is working great.
However, I don't know how to access each item in ValData after the function returns. I can get one at a time like: ValidateForm().IsValid, but in order to get each item, I have to run the function again. I want to avoid this.
Is there a way to run the function once, but access the values of each item returned?
Depending upon your requirements (which are NOT clear in your question! ;-) ), you might consider using a Collection as the return from your function:
Private Function MyResultsFunction() As Collection
Dim output As Collection
Set output = New Collection
'Hydrate your collection with data by whatever means necessary:
With output
'Stupid example code:
.Add "Item1"
.Add "Item2"
.Add "Item3"
.Add "Item4"
End With
'Return a reference to the collection as the function output:
Set MyResultsFunction = output
End Function
As a simple, retarded test of the above:
Private Sub Form_Load()
'A variable to receive the results from your function:
Dim Results As Collection
'An iterator to loop through the results contained in the collection:
Dim CurrentItem As Variant
'For this example, a string to toss the results into to display in the
'MsgBox:
Dim output As String
'Use the local Collection defined above to access the reference returned by
'your function:
Set Results = MyResultsFunction
'Iterate through the collection and do something with the results
'per your project requirements:
For Each CurrentItem In Results
'This is stupid example code:
output = output & CurrentItem & vbCrLf
Next
'I am just displayng the results to show that it worked:
MsgBox output
'Clean up:
Set Results = Nothing
End Sub
Hope that heps!
Without seeing your code it's hard to say what exactly you are tyring to do, but here is one of several ways you can store results to query later.
Declare a public variable at the top of your module to represent the items from your ValData function. After you populate the array, you can access the items through a normal function.
You obviously could do more sophisticated things, especially if you use a collection object. You could even store a counter and create a GetNext() function. I hope this gives you a heads start.
Public Results(1 To 2) As String
Sub CreateTestArray()
Results(1) = "Foo"
Results(2) = "Bar"
End Sub
Function GetResult(ByVal i As Long) As String
If i <= UBound(Results) Then
GetResult = Results(i)
End If
End Function
Sub ProofOfConcept()
MsgBox GetResult(2) ' will show "Bar"
End Sub

Is there a NotIn("A","B") function in VBA?

I'm writing a function that requires input and my data validation looks very awkward. If InputFld isn't "A","B", or "C", then it's an error:
If InputFld <>"A" and InputFld<>"B" and InputFld<>"C" then goto ErrorHandler
This just looks ugly to me. Is there a more elegant solution? I'd like to just write something like:
If InputFld not in ("A","B","C") then goto ErrorHandler
See? Much easier to read and maintain this way. But I don't know how to do it.
How about:
If Instr("ABC",InputFld)=0 Then
At least two ways to do that:
public function IsInSet(byval value as variant, paramarray theset() as variant) as boolean
dim i as long
for i=lbound(theset) to ubound(theset)
if value = theset(i) then
isinset = true
exit function
end if
next
end function
Usage: If not IsInSet(val, "A", "B", "C") then ...
public function IsInSet(byval value as variant, theset as variant) as boolean
dim i as long
for i=lbound(theset) to ubound(theset)
if value = theset(i) then
isinset = true
exit function
end if
next
end function
Usage: If not IsInSet(val, array("A", "B", "C")) then ...
Eval() should allow you to do something similar. This expression returns -1 (True):
Debug.Print Eval("""g"" Not In (""a"",""b"",""c"")")
I wouldn't call that elegant, though.
Consider using the Like operator. This expression returns True:
Debug.Print Not "g" Like "[abc]"

Replace Module Text in MS Access using VBA

How do I do a search and replace of text within a module in Access from another module in access? I could not find this on Google.
FYI, I figured out how to delete a module programatically:
Call DoCmd.DeleteObject(acModule, modBase64)
I assume you mean how to do this programatically (otherwise it's just ctrl-h). Unless this is being done in the context of a VBE Add-In, it is rarely (if ever) a good idea. Self modifying code is often flagged by AV software an although access will let you do it, it's not really robust enough to handle it, and can lead to corruption problems etc. In addition, if you go with self modifying code you are preventing yourself from ever being able to use an MDE or even a project password. In other words, you will never be able to protect your code. It might be better if you let us know what problem you are trying to solve with self modifying code and see if a more reliable solution could be found.
After a lot of searching I found this code:
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Function to Search for a String in a Code Module. It will return True if it is found and
'False if it is not. It has an optional parameter (NewString) that will allow you to
'replace the found text with the NewString. If NewString is not included in the call
'to the function, the function will only find the string not replace it.
'
'Created by Joe Kendall 02/07/2003
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Function SearchOrReplace(ByVal ModuleName As String, ByVal StringToFind As String, _
Optional ByVal NewString, Optional ByVal FindWholeWord = False, _
Optional ByVal MatchCase = False, Optional ByVal PatternSearch = False) As Boolean
Dim mdl As Module
Dim lSLine As Long
Dim lELine As Long
Dim lSCol As Long
Dim lECol As Long
Dim sLine As String
Dim lLineLen As Long
Dim lBefore As Long
Dim lAfter As Long
Dim sLeft As String
Dim sRight As String
Dim sNewLine As String
Set mdl = Modules(ModuleName)
If mdl.Find(StringToFind, lSLine, lSCol, lELine, lECol, FindWholeWord, _
MatchCase, PatternSearch) = True Then
If IsMissing(NewString) = False Then
' Store text of line containing string.
sLine = mdl.Lines(lSLine, Abs(lELine - lSLine) + 1)
' Determine length of line.
lLineLen = Len(sLine)
' Determine number of characters preceding search text.
lBefore = lSCol - 1
' Determine number of characters following search text.
lAfter = lLineLen - CInt(lECol - 1)
' Store characters to left of search text.
sLeft = Left$(sLine, lBefore)
' Store characters to right of search text.
sRight = Right$(sLine, lAfter)
' Construct string with replacement text.
sNewLine = sLeft & NewString & sRight
' Replace original line.
mdl.ReplaceLine lSLine, sNewLine
End If
SearchOrReplace = True
Else
SearchOrReplace = False
End If
Set mdl = Nothing
End Function
Check out the VBA object browser for the Access library. Under the Module object you can search the Module text as well as make replacements. Here is an simple example:
In Module1
Sub MyFirstSub()
MsgBox "This is a test"
End Sub
In Module2
Sub ChangeTextSub()
Dim i As Integer
With Application.Modules("Module1")
For i = 1 To .CountOfLines
If InStr(.Lines(i, 1), "This is a Test") > 0 Then
.ReplaceLine i, "Msgbox ""It worked!"""
End If
Next i
End With
End Sub
After running ChangeTextSub, MyFirstSub should read
Sub MyFirstSub()
MsgBox "It worked!"
End Sub
It's a pretty simple search but hopefully that can get you going.
additional for the function (looping through all the lines)
Public Function ReplaceWithLine(modulename As String, StringToFind As String, NewString As String)
Dim mdl As Module
Set mdl = Modules(modulename)
For x = 0 To mdl.CountOfLines
Call SearchOrReplace(modulename, StringToFind, NewString)
Next x
Set mdl = Nothing
End Function
Enjoy ^^