2012-01-26 22 views
13

Nota il tag: VBA, non VB6, VB.NET non.callback personalizzati in VBA

Questo è specifico per VBA in MS Access. Ho creato una raccolta di metodi in un modulo che chiamo "Enumerable". Fa un sacco di cose che ricordano le classi e le interfacce Enumerable in .NET. Una cosa che voglio implementare è un metodo ForEach, analogo a .NET Enumerable.Select method.

Ho creato una versione che utilizza il metodo Application.Run per chiamare una funzione per ciascun elemento, ma Application.Run funziona solo con metodi definiti dall'utente. Ad esempio, i seguenti lavori:

' 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 

Esegui stampa "Uscita", come previsto. La seguente non funziona:

Debug.Print Run("Replace", "Input", "In", "Out") 

Si genera un errore in fase di esecuzione 430: "La classe non supporta l'automazione o non supporta l'interfaccia prevista". Questo è previsto, poiché la documentazione afferma che Application.Run funziona solo con metodi definiti dall'utente.

VBA ha un operatore AddressOf, ma che funziona solo quando passa puntatori alla funzione di funzioni API esterne; i puntatori di funzione creati usando AddressOf non sono consumabili in VBA. Di nuovo, questo è indicato nella documentazione (o si veda ad esempio VBA - CallBacks = Few Cents Less Than A Dollar?).

Quindi non v'è altro modo per identificare e chiamare un metodo che utilizza una variabile? O i miei tentativi di callback-ish saranno limitati alle funzioni definite dall'utente tramite il metodo Application.Run?

+4

You wont essere in grado di utilizzare addressof, ma VBA supporta le classi in modo che i callback basati su interfaccia siano soddisfacenti; 'sub qualcosa (arg come stringa, myCallBack come IWhatever) ... myCallBack.call (...)' Inserisci i tuoi metodi in una classe e puoi invocarli per nome con "CallByName" - è un po 'debole a causa della gestione di numeri dinamici di argomenti, un'alternativa http://www.devx.com/tips/Tip/15422 –

+0

Sono un po 'confuso su quello con cui hai effettivamente bisogno di aiuto. Il tuo primo paragrafo parla dell'implementazione di 'Per ...Ognuno, ma il resto della tua domanda parla di provare ad usare 'Application.Run' su una funzione VBA. – mischab1

+1

@ mischab1 - Dai un'occhiata a tutti i metodi di estensione .NET IEnumerable. Nella maggior parte di essi viene fornito un callback da eseguire su ciascun membro di una raccolta. Sto cercando di fare la stessa cosa in VBA. VBA non ha delegati o puntatori di funzione o altro. Ho usato Application.Run come sostituto, ma ha applicabilità limitata. –

risposta

5

Non ci sono altre risposte in una settimana ... per l'amor di risoluzione qui è il meglio che ho potuto venire con:

  1. ho costruito un modulo di supporto che consente di risolvere un ParamArray a singoli argomenti per il bene di chiamare CallByName. Se si passa un ParamArray a CallByName, esso schiaccia tutti gli argomenti in un singolo, effettivo Array e lo passa al primo argomento nel metodo che si tenta di richiamare.
  2. Ho creato due metodi ForEach: uno che richiama Application.Run e un altro che richiama CallByName. Come indicato nella domanda, Application.Run funziona solo per i metodi definiti dall'utente globali (modulo pubblico). A sua volta, CallByName funziona solo su metodi di istanza e richiede un argomento oggetto.

Questo mi lascia ancora senza un modo per invocare direttamente metodi globali incorporati (come ad esempio Trim()) per nome. La mia soluzione per che è quello di costruire metodi di wrapper definiti dall'utente che basta chiamare il built-in metodo globale, ad esempio:

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... 

ora posso usare questi per fare le cose come:

' 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") 
+0

Intendi, 'FLTrim' qui' trimmedArray = ForEachRun (rawArray, "FTrim") '? – bonCodigo

+1

@bonCodigo No, 'Trim' e' LTrim' sono funzioni VBA separate. Nel codice in alto mostro un esempio del wrapper per 'LTrim', e nell'esempio in basso io uso il wrapper per' Trim'. –

+0

Ho un paio di suggerimenti alternativi, saresti comunque interessato alle risposte alla domanda? – Blackhawk