Eval scope in Access VBA - ms-access

Please help.
=Does not work:
Function f()
f = 1
End Function
Private Sub Button1_Click()
k = Eval("f()")
End Sub
=Does not work:
Private Sub Button1_Click()
с=1
k = Eval("с")
End Sub
=Do work:
Function f()
f = 1
End Function
Private Sub Button1_Click()
k = f()
End Sub
=In help:
The following example assumes that you have a series of 50 functions defined as A1, A2, and so on. This example uses the Eval function to call each function in the series.
Sub CallSeries()
Dim intI As Integer
For intI = 1 To 50
Eval("A" & intI & "()")
Next intI
End Sub
How to make variant 1 work?
Thanks in advance.
++++++++++++++++++++++++++++
=UPDATE:
The number of error I get with "Does not work" sections is 2425. "Can not find the name of the function." (or "Can not find the name of the expression." in the second case). Sorry for my English.
=UPDATE:
When I try explicitly name the function in Eval:
k = Eval("[Forms]![Form1].f()")
I get error 31005 - "Access can not calculate expression because there is a reference to "f"". I start to be suspicious of Access prohibiting use of user-defined functions and variables in Eval. Though help states opposite.
=UPDATE:
Do work:
Function f()
f = 1
End Function
Private Sub Button1_Click()
k = Eval("val(""551" & "9" & "6"")")
End Sub
Very strange rules of double-quoting, as for me. But this does not solve my problem.

The Eval function cannot access functions that are in Form modules. Place the function in an ordinary Module.

There is one interesting fact. Eval() does not accept user variables as arguments, but accepts form controls data.

Related

Byref vs Byval vba clarification

I was writing some VBA to futher my understanding of Byref and Byval. This is what I am using:
Private Function Checkcase()
Dim i As Integer
i = 1
Debug.Print i
num(i)
Debug.Print i
End Function
Public Function num(ByRef i As Integer)
i = 5
End Function
Output
1
1
However, when I use the call keyword in front of num(i) like:
call num(i)
The output changes to
1
5
I was expecting the output to be 1 and 5 in both cases as I am changing the reference of the variable i
num(i) is the error here. When you call a sub or a function without getting the return value, you must not put parentheses around the arguments.
When you pass arguments with parentheses to a function expecting ByRef arguments, those are automatically cast to ByVal.
Either use Call num(i) or remove the parentheses num i
References: Call :
If you omit the Call keyword, you also must omit the parentheses around argumentlist.
As always, Cpearson site is a must read

VBA Class Module: Runtime error 438 Object doesn't support this property or method

I have the following class module BlSecurity in my Access database:
Option Compare Database
Option Explicit
Private tiendaRepo As ITiendaRepository
Public Sub Init(ptiendaRepo As ITiendaRepository)
tiendaRepo = ptiendaRepo
End Sub
Public Sub Login(code As String)
If tiendaRepo.CheckIfCodeExists(code) = "" Then
Err.Raise Number:=CustomErrors.CenterCodeNotExisting
End If
Exit Sub
End Sub
Private Sub Class_Terminate()
Set tiendaRepo = Nothing
End Sub
This is TiendaRepository code:
Option Compare Database
Option Explicit
Implements ITiendaRepository
Public Function ITiendaRepository_CheckIfCodeExists(ByVal code As String) As String
Err.Raise vbObjectError, "CheckCode", "Not implemented"
Exit Function
End Function
And this is the "interface" ITiendaRepository I'm implementing:
Option Compare Database
Option Explicit
Public Function CheckIfCodeExists(ByVal code As String) As String
End Function
Then, inside a button handler:
Private Sub btnLogin_Click()
Dim bl As blSecurity
Set bl = New blSecurity
bl.Init (New TiendaRepository)
bl.Login (txtUsuario.Value)
End Sub
But when I click the button, I receive message:
Object doesn't support this property or method
in line bl.Init (New TiendaRepository)
. What's wrong with it?
It runs (i.e., raises the "Not implemented" message) on my test system with these two changes:
In your button-click module, remove the parentheses around New TiendaRepository.
Private Sub btnLogin_Click()
Dim bl As BlSecurity
Set bl = New BlSecurity
bl.Init New TiendaRepository ' <=== here
bl.Login txtUsuario.Value
End Sub
This is because VBA doesn't use parentheses when calling subroutines and not collecting a return value. If you add the parentheses, they actually cause evaluation of the default property. Therefore, instead of passing the New TiendaRepository object to bl.Init, you are passing whatever VBA thinks the default value is.
Note that the VBA editor put a space before the opening parenthesis in your code. That's a visual clue that it's not doing what you might expect coming from languages that always use parens on calls.
In BlSecurity.Init, add a Set:
Public Sub Init(ptiendaRepo As ITiendaRepository)
Set tiendaRepo = ptiendaRepo
End Sub
That is because you always (as far as I know) need Set when you are assigning objects (internally, references to objects).
If you want to use parentheses around method calls (not function calls) in VBA, you use the Call keyword. That is,
Call bl.Init(New TiendaRepository)
is the same as
bl.Init New TiendaRepository
This is true regardless of the number of parameters — Call foo(a,b,c) is the same as foo a, b, c.
Maybe
1) Note of set keyword
2) Removal of () in call
3) BlSecurity must also implement ITiendaRepository_CheckIfCodeExists
BlSecurity
Option Compare Database
Option Explicit
Implements iTiendaRepository
Private tiendaRepo As iTiendaRepository
Public Sub Init(ptiendaRepo As iTiendaRepository)
Set tiendaRepo = ptiendaRepo '*1
End Sub
Public Sub Login(code As String)
If tiendaRepo.CheckIfCodeExists(code) = "" Then
Err.Raise Number:=CustomErrors.CenterCodeNotExisting
End If
Exit Sub
End Sub
Private Sub Class_Terminate()
Set tiendaRepo = Nothing
End Sub
Public Function ITiendaRepository_CheckIfCodeExists(ByVal code As String) As String '*3
Err.Raise vbObjectError, "CheckCode", "Not implemented"
Exit Function
End Function
Module
Option Compare Database
Option Explicit
Private Sub btnLogin_Click()
Dim bl As BlSecurity
Set bl = New BlSecurity
bl.Init New TiendaRepository '*2
bl.Login txtUsuario.Value '<=== Not sure where declare and should remove ()
End Sub
Though I am unsure where you have declared txtUsuario

How do I use a variable from a different function in another function?

I have one method
Public CurrentFileNameNoExtension As String
Public Sub importexcelfile()
CurrentFileNameNoExtension ="Filename"
'do something
End Sub
I want to use CurrentFileNameNoExtension value in onEnter event of the dropdown list(cmvalues) event. That Value use in sql query. My code is
Private Sub cmvalues_Enter()
Dim qstng As String
qstng = CurrentFileNameNoExtension
Me.cmvalues.RowSourceType = "Table/Query"
Me.cmvalues.RowSource = "Select F1 from " & qstng & " WHERE F1 <> 'Control Model';"
End Sub
But qstng value is empty. it is not giving the value in the importexcelfile() function.
EDIT: As I've just noticed, thanks to #simoco, that this is indeed for a userform, there are actually a couple of things to pull this off. One is using globals, which is quite tricky, and another is to use a function to get the string you want.
Function CurrentFileNameNoExtension() As String
'Do some FSO or GetOpenFileName here.
CurrentFileNameNoExtension = "Filename"
End Sub
Private Sub cmvalues_Enter()
qstng = CurrentFileNameNoExtension
Me.cmvalues.RowSourceType = "Table/Query"
Me.cmvalues.RowSource = "Select F1 from " & strFileName & " WHERE F1 <> 'Control Model';"
End Sub
There is not much of an issue using the code you have, really. You just have to make sure that the first sub is called before the second one so that cmvalues_Enter has a valid string to process.
Place this function under Microsoft Access Class Objects Form control,Where cmvalues dropdown exists
Public CurrentFileNameNoExtension As String
Public Sub importexcelfile()
CurrentFileNameNoExtension ="Filename"
'do something
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")

Is there a way to always execute some code before Exit Function in VBScript

I would like to write a function in which there are multiple "Exit Function" statements. Is there a way for me to make sure some clean up code is alway run before doing the exit function?
Here is what I'm actually trying to do, I want to set a standard in my test automation project, that certain function should run before the code of any function begins and run another function before exiting out of the function. Is there any simple way of doing this?
As wrapping functions in functions isn't easy in VBScript (expect problems of parameter passing), you should use a class:
Class cWrap
Private m_sName
Public Function init(s)
Set init = Me
m_sName = s
WScript.Echo "*** enter", m_sName
End Function ' init
Private Sub Class_Terminate()
WScript.Echo "*** leave", m_sName
End Sub ' Class_Terminate
End Class ' cWrap
Function f1(n)
f1 = "f1 called"
Dim o : Set o = New cWrap.init("f1")
End Function ' f1
Function f2(n)
f2 = "f2 called"
Dim o : Set o = New cWrap.init("f2")
End Function ' f2
The wrapper class of Ekkehard.Horner is very usefull if you have stuff to do that can be out of scope of the function, like a stopwatch, a runstack viewer or an exception trapper on the err object. But when you have things to do inside the scope of the function and the wrapper class renders useless, you could consider using the Do Loop While False pattern:
Function DoSomeMessyStuff()
Do
call doStuff()
If somecondition Then Exit Do
call doMoreStuff()
If somecondition Then Exit Do
call doEvenMoreStuff()
If somecondition Then Exit Do
call andEvenMoreStuffHere()
Loop While False ' Always exits
' Finally:
call cleanUpTheMess()
End Function