Пользовательские обратные вызовы в VBA
Обратите внимание на тег: VBA, а не VB6, а не VB.NET.
Это относится к VBA в MS Access. Я создал набор методов в модуле, который я называю "Перечислимым". В нем много чего напоминают классы Enumerable и интерфейсы в .NET. Одна вещь, которую я хочу реализовать, - это метод ForEach, аналогичный методу .NET Enumerable.Select.
Я построил версию, которая использует метод Application.Run для вызова функции для каждого элемента, но приложение .Run работает только с пользовательскими методами. Например, следующие работы:
' 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 печатает "Выход", как и ожидалось. Не работает следующее:
Debug.Print Run("Replace", "Input", "In", "Out")
Он выдает ошибку времени выполнения 430: "Класс не поддерживает автоматизацию или не поддерживает ожидаемый интерфейс". Ожидается, что в документации указано, что Application.Run работает только для пользовательских методов.
У VBA есть оператор AddressOf, но это работает только при передаче указателей функций на внешние API-функции; указатели функций, созданные с помощью AddressOf, не расходуются в VBA. Опять же, это отмечается в документации (или см., Например, VBA - CallBacks = несколько центов меньше, чем доллар?).
Итак, есть ли другой способ идентифицировать и вызывать метод с использованием переменной? Или мои попытки обратного вызова будут ограничены пользовательскими функциями через метод Application.Run?
Ответы
Ответ 1
Никаких других ответов за неделю... для разрешения здесь лучшее, что я мог придумать:
- Я построил вспомогательный модуль, который разрешает ParamArray для отдельных аргументов ради вызова
CallByName
. Если вы передадите параметр 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")