Returning Multiple Values from a Custom Function - ms-access

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

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/

VBA Array Declaration Using a Variable

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

Passing parameters in VBA events

I am relatively new to Access though I do have some experience in VB, my question is probably very simple though I don't seem to know the terminology to search in order to find an answer I can use.
I am in the process of creating an "OnChange" event for a tab control I am using, I would like to pass an undetermined amount of integers to the function. IE:
=myFunction(1,4,6) OR =myFunction(ArrayList[1,2,4])
I would either create an overloaded function to work with these numbers, or if possible I would like to pass them as an array of integers. Though for the life of me I cannot figure out exactly how to do this. The reason I have taken this path is to make my function as universal as possible, basically just having to change what numbers I send to the function to change its behaviour.
This is some rough coding of what I am try to do, though I have no idea how to pass anything besides something like =myFunction([Form])
Public Function Refresh(tabsToCheck As ArrayList)
For Each o In tabsToCheck
If Me.DevForm.Value = o Then
RefreshAllForms
End If
Next o
End Function
Public Function RefreshAllForms()
Dim f As Form
For Each f In Access.Forms
f.Refresh
Next
End Function
Update
I thought I would update with my finalized code in case anyone needs this in the future thanks for your help!
Public Function RefreshControlTab(ctrl As Access.Control, ParamArray TabsToRefresh())
Dim i As Long
Dim lngUBound As Long
If UBound(TabsToRefresh) >= 0 Then
lngUBound = UBound(TabsToRefresh)
For i = 0 To lngUBound
If ctrl.Value = (TabsToRefresh(i) - 1) Then
RefreshAllForms
End If
Next
End If
End Function
Public Function RefreshAllForms()
Dim f As Form
For Each f In Access.Forms
f.Refresh
Next
End Function
So one change you would say '=RefreshControlTab([DevForm],3,4)' and when the 3rd or 4th tab is selected a refresh will be performed.
"I would like to pass some an undetermined amount of integers to the function."
That sounds like a ParamArray to me. See the simple function below. It will return the sum of a set of numbers.
Public Function AddThem(ParamArray MyNumbers()) As Long
Dim i As Long
Dim lngReturn As Long
Dim lngUBound As Long
If UBound(MyNumbers) >= 0 Then
lngUBound = UBound(MyNumbers)
For i = 0 To lngUBound
lngReturn = lngReturn + MyNumbers(i)
Next
End If
AddThem = lngReturn
End Function
Note the ParamArray is an array of Variant values. So within the function you would need to verify the values are numbers to avoid trouble ... one example of trouble would be a "type mismatch" error when calling the function with string values: AddThem("a", "b")

returning a two dimensional array from a function in vbscript

im having an issue with a program im working on.
what im trying to do is have a function accept input from a user and store that data in an array
for small testing purposes it is a 3 x 3 array
i have gotten the array within the function to work as tested by echoing out all values stored.
however when i attempt to return the array to the sub from which it is called i get mismatch errors, im not sure what i am doing wrong.
Sub SubroutineA()
Dim Array(2,2)
Array = GetInfo()
End Sub
Function GetInfo()
Dim FunctionArray(2,2)
{input all data into array}
GetInfo = FunctionArray()
End Function
Any Help i could get would be great as this is new to me.
Cheran Shunmugavel points to the right direction, but his explanation contains an ambiguety. To make it clear:
Sub SubroutineA()
Dim Arr ' <<<--- do not use parenthesis here and do not use
' the reserved keyword "Array"
Arr = GetInfo()
End Sub
Function GetInfo()
Dim FunctionArray(2,2)
' {input all data into array}
GetInfo = FunctionArray ' <<<--- do not use parenthesis here
End Function

Referencing global variable - MS Access

Im trying to pass the name of a global variable to a sub routine and would like to know how to reference it. For example I could do the below with a control:
Private Sub passCtrlName(ctlName as String)
Me.Controls(ctlName) = "Whatever"
End Sub
Edit:
For Example
Public imGlobVar As String
Public Sub passGlobVar(frm as Form, ctlName as String, globVar as String)
frm.Controls(ctlName) = globVar
End sub
And call it as
Private Sub imaButton_Click()
imGlobVar = "something"
Call passGlobVar(Me , txtBox1, imGlobVar)
End sub
2nd Edit:
It seems that I could most definitely be barking up the wrong tree here, so I will explain what I'm trying to achieve.
I have a form that has textboxes for the users (risk) address, with a checkbox at the top that lets the user select that this address is the same as the 'contact' details already on the system, and the textboxes are locked.
Populating the textboxes is fine and works. What I use the global variables for is to improve usability (albeit slightly).
The user can add new details, and if they hit the checkbox 'make same as contact' the details that they have entered are stored in the global variables, one for each control.
If the user has made a mistake by hitting the checkbox, they haven't lost these value, and by unchecking the box the entered values are returned.
I hoped to create a sub routine where I could pass the name of the global variable and control and calling this routine, as opposed to writing it out for each control.
I have a feeling that I could be using the wrong technique to achieve my goals. But in answer to my original question, it appears that you can not pass global variables to sub routines in the manner that I wished.
You do not need to pass global variables, you can simply refer to them by name. Note that global variables are reset if an unhandled error occurs.
In http://msdn.microsoft.com/en-us/library/dd897495(office.12).aspx you will find a section on Scope and Lifetime of Variables and Constants.
In a module:
Option Explicit
Public glbVarName As String
Const MyConstant=123
Sub InitVar
glbVarName="Something"
End Sub
Any other module, includeing a form's class module:
Sub SomeSub
MsgBox glbVarName
SomeVar=2+MyConstant
End Sub
If you're asking if you can dynamically reference global variables using a string containing the variable name the answer is no. You could use a single global array and pass the index, which would allow you to dynamically reference an element of the array.
[Edit]
In response to the clarification in the question: You could just save the value of each control to its Tag property when the user checks the checkbox. Then, if the user unchecks the checkbox, you can just loop over your controls and assign the value from the Tag back to the Value of the control.
You could store the values from your controls in a Dictionary object, using the control names as the dictionary keys. Then you can retrieve the value for each control based on the control's name.
Option Compare Database
Option Explicit
Const cstrMyControls As String = "Text0,Text2,Text4,Text6"
Dim objDict As Object
Private Sub chkToggle_Click()
If Me.chkToggle = True Then
Call SaveValues
Else
Call RestoreValues
End If
End Sub
Private Sub SaveValues()
Dim varControls As Variant
Dim i As Long
Set objDict = Nothing 'discard previous saved values '
Set objDict = CreateObject("Scripting.Dictionary")
varControls = Split(cstrMyControls, ",")
For i = 0 To UBound(varControls)
objDict.Add varControls(i), Me.Controls(varControls(i)).Value
Next i
End Sub
Private Sub RestoreValues()
Dim varControls As Variant
Dim i As Long
If objDict Is Nothing Then
'MsgBox "No values to restore." '
Else
varControls = objDict.keys()
For i = 0 To UBound(varControls)
Me.Controls(varControls(i)).Value = objDict(varControls(i))
Next i
End If
End Sub
I use additional field in table - name cancel - of course boolean - when i'm not sure if contents of fields will be valid I set it true. If this field will be true by the end - then I clean up (it may be all record or some fileds of course). Very easy.