VBA中的自定义回调
注意标签:VBA,不是VB6,不是VB.NET 这是MS Access中的VBA特有的。我在一个称为“Enumerable”的模块中构建了一组方法。它做了很多让人想起.NET中可枚举类和接口的事情。我想实现的一件事是ForEach方法,它与.NET非常相似 我构建了一个版本,使用该方法为每个元素调用函数,但Application.Run仅适用于用户定义的方法。例如,以下工作:VBA中的自定义回调,vba,callback,Vba,Callback,注意标签:VBA,不是VB6,不是VB.NET 这是MS Access中的VBA特有的。我在一个称为“Enumerable”的模块中构建了一组方法。它做了很多让人想起.NET中可枚举类和接口的事情。我想实现的一件事是ForEach方法,它与.NET非常相似 我构建了一个版本,使用该方法为每个元素调用函数,但Application.Run仅适用于用户定义的方法。例如,以下工作: ' User-defined wrapper function: Public Function MyReplace(
' 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
运行测试按预期打印“输出”。以下操作不起作用:
Debug.Print Run("Replace", "Input", "In", "Out")
它抛出运行时错误430:“类不支持自动化或不支持预期接口”。这是预期的,因为文档说明Application.Run仅适用于用户定义的方法
VBA确实有一个AddressOf运算符,但它只在将函数指针传递给外部API函数时起作用;使用AddressOf创建的函数指针在VBA中不可用。同样,文档中也提到了这一点(或参见示例)
那么,有没有其他方法来识别和调用使用变量的方法呢?或者,我的回调尝试将通过Application.Run方法局限于用户定义的函数吗?一周内没有其他答案……为了解决这个问题,这里是我能想到的最好答案:
CallByName
,我构建了一个助手模块,将ParamArray解析为各个参数。如果将ParamArray传递给CallByName
,它会将所有参数合并成一个实际的Array
,并将其传递给尝试调用的方法中的第一个参数ForEach
方法:一个调用Application.Run
,另一个调用CallByName
。如问题中所述,Application.Run
仅适用于用户定义的全局(公共模块)方法。反过来,CallByName
只对实例方法有效,并且需要一个对象参数李>
这仍然使我无法按名称直接调用内置全局方法(例如Trim()
)。我的解决方法是构建只调用内置全局方法的用户定义包装器方法,例如:
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...
我现在可以用这些来做以下事情:
' 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")
非常老的问题,但对于那些寻求更通用方法的人,请使用
stdCallback
和stdLambda
以及stdicalable
。这些可以作为图书馆的一部分找到
您将无法使用addressof,但VBA支持类,因此基于接口的回调是可以的<代码>子对象(arg作为字符串,myCallBack作为IWhatever)。。。myCallBack.call(…)将方法塞入一个类中,您可以使用“CallByName”按名称调用它们-由于处理动态数量的参数,这有点弱,另一种选择是我对您实际需要的帮助有点困惑。您的第一段讨论了为…每个实现
,但问题的其余部分讨论了如何尝试使用应用程序。在VBA函数上运行
。@MISHAB1-查看任何.NET IEnumerable扩展方法。在大多数情况下,您提供了一个回调来对集合的每个成员执行。我正试图在VBA中做同样的事情。VBA没有委托、函数指针或任何东西。我使用了Application.Run作为替代,但它的适用性有限。您的意思是,FLTrim
这里trimmedArray=ForEachRun(rawArray,“FTrim”)
?@bonCodigo No、Trim
和LTrim
是独立的VBA函数。在上面的代码中,我展示了LTrim
的包装器示例,而在下面的示例中,我使用Trim
的包装器。我有几个备选建议,您是否仍然对这个问题的答案感兴趣?@Blackhawk,我刚刚通过搜索引擎找到了这个问题,所以我想可以安全地假设答案是肯定的,有人会的interested@user357269有关VBA中第一类函数的实现(例如,可以存储在变量中、传递给函数并匿名调用的函数),请参阅。
sub Main()
'Create an array
Dim arr as stdArray
set arr = stdArray.Create(1,2,3,4,5,6,7,8,9,10) 'Can also call CreateFromArray
'Demonstrating join, join will be used in most of the below functions
Debug.Print arr.join() '1,2,3,4,5,6,7,8,9,10
Debug.Print arr.join("|") '1|2|3|4|5|6|7|8|9|10
'Basic operations
arr.push 3
Debug.Print arr.join() '1,2,3,4,5,6,7,8,9,10,3
Debug.Print arr.pop() '3
Debug.Print arr.join() '1,2,3,4,5,6,7,8,9,10
Debug.Print arr.concat(stdArray.Create(11,12,13)).join '1,2,3,4,5,6,7,8,9,10,11,12,13
Debug.Print arr.join() '1,2,3,4,5,6,7,8,9,10 'concat doesn't mutate object
Debug.Print arr.includes(3) 'True
Debug.Print arr.includes(34) 'False
'More advanced behaviour when including callbacks! And VBA Lamdas!!
Debug.Print arr.Map(stdLambda.Create("$1+1")).join '2,3,4,5,6,7,8,9,10,11
Debug.Print arr.Reduce(stdLambda.Create("$1+$2")) '55 ' I.E. Calculate the sum
Debug.Print arr.Reduce(stdLambda.Create("Max($1,$2)")) '10 ' I.E. Calculate the maximum
Debug.Print arr.Filter(stdLambda.Create("$1>=5")).join '5,6,7,8,9,10
'Execute property accessors with Lambda syntax
Debug.Print arr.Map(stdLambda.Create("ThisWorkbook.Sheets($1)")) _
.Map(stdLambda.Create("$1.Name")).join(",") 'Sheet1,Sheet2,Sheet3,...,Sheet10
'Execute methods with lambdas and enumerate over enumeratable collections:
Call stdEnumerator.Create(Application.Workbooks).forEach(stdLambda.Create("$1#Save")
'We even have if statement!
With stdLambda.Create("if $1 then ""lisa"" else ""bart""")
Debug.Print .Run(true) 'lisa
Debug.Print .Run(false) 'bart
End With
'Execute custom functions
Debug.Print arr.Map(stdCallback.CreateFromModule("ModuleMain","CalcArea")).join '3.14159,12.56636,28.274309999999996,50.26544,78.53975,113.09723999999999,153.93791,201.06176,254.46879,314.159
'Creating from an object property
Debug.Print arr.Map(stdCallback.CreateFromObjectProperty(arr,"item", vbGet)) '1,2,3,4,5,6,7,8,9,10
'Creating from an object method
Debug.Print arr.Map(stdCallback.CreateFromObjectMethod(someObj,"getStuff"))
End Sub
Public Function CalcArea(ByVal radius as Double) as Double
CalcArea = 3.14159*radius*radius
End Function