Подтвердить что ты не робот

Пользовательские обратные вызовы в 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?

4b9b3361

Ответ 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")