Note the tag: VBA, not VB6, not VB.NET.
This is specific to VBA in MS Access. I've built a collection of methods in an module I call "Enumerable". It does a lot of things reminiscent of the Enumerable classes and interfaces in .NET. One thing I want to implement is a ForEach method, analagous to the .NET Enumerable.Select method.
I built a version that uses the Application.Run method to call a function for each element, but Application.Run only works with user-defined methods. For example, the following works:
' User-defined wrapper function:
Public Function MyReplace( _
Expression As String, Find As String, StrReplace As String, _
Optional Start As Long = 1, _
Optional Count As Long = 1, _
Optional Compare As VbCompareMethod = vbBinaryCompare)
MyReplace = Replace(Expression, Find, StrReplace, Start, Count, Compare)
End Function
' Using Application.Run to call a method by name
Public Sub RunTest()
Debug.Print Run("MyReplace", "Input", "In", "Out")
End Sub
RunTest prints "Output", as expected. The following does NOT work:
Debug.Print Run("Replace", "Input", "In", "Out")
It throws run-time error 430: "Class does not support Automation or does not support expected interface". This is expected, because the documentation states that Application.Run only works for user-defined methods.
VBA does have an AddressOf operator, but that only works when passing function pointers to external API functions; function pointers created using AddressOf are not consumable in VBA. Again, this is noted in the documentation (or see for example VBA - CallBacks = Few Cents Less Than A Dollar?).
So is there any other way to identify and call a method using a variable? Or will my callback-ish attempts be limited to user-defined functions via the Application.Run method?
No other answers in a week...for resolution's sake here's the best I could come up with:
CallByName
. If you pass a ParamArray through to CallByName
it will mash all the arguments into a single, actual Array
and pass that to the first argument in the method you attempt to invoke.ForEach
methods: one that invokes Application.Run
, and another that invokes CallByName
. As noted in the question, Application.Run
only works for user-defined global (public module) methods. In turn, CallByName
only works on instance methods, and requires an object argument. That still leaves me without a way to directly invoke built-in global methods (such as Trim()
) by name. My workaround for that is to build user-defined wrapper methods that just call the built-in global method, for example:
Public Function FLeft( _
str As String, _
Length As Long) As String
FLeft = Left(str, Length)
End Function
Public Function FLTrim( _
str As String) As String
FLTrim = LTrim(str)
End Function
Public Function FRight( _
str As String, _
Length As Long) As String
FRight = Right(str, Length)
End Function
...etc...
I can now use these to do things like:
' Trim all the strings in an array of strings
trimmedArray = ForEachRun(rawArray, "FTrim")
' Use RegExp to replace stuff in all the elements of an array
' --> Remove periods that aren't between numbers
Dim rx As New RegExp
rx.Pattern = "(^|\D)\.(\D|$)"
rx.Global = True
resultArray = ForEachCallByName(inputArray, rx, "Replace", VbMethod, "$1 $2")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With