Notazione del botto e notazione a punti in VBA e MS-Access

Durante la lettura di un’applicazione che sto documentando , ho analizzato alcuni esempi di notazione bang nell’accedere alle proprietà / metodi dell’object, ecc. E in altri punti usano notazione a punti per quello che sembra lo stesso scopo.

C’è differenza o preferenza nell’usare l’una o l’altra? Alcuni semplici googling rivelano solo informazioni limitate sull’argomento con alcune persone che effettivamente lo usano in casi opposti. Forse da qualche parte c’è una sezione sugli standard di codifica da MS che indica il metodo della follia?

Nonostante la (precedentemente) risposta accettata a questa domanda, il botto non è in realtà un membro o un operatore di accesso alla raccolta. Fa una cosa semplice e specifica: l’operatore bang fornisce l’accesso tardivo al membro predefinito di un object, passando il nome letterale che segue l’operatore bang come argomento stringa a quel membro predefinito.

Questo è tutto. L’object non deve essere una raccolta. Non deve avere un metodo o una proprietà chiamata Item . Tutto ciò di cui ha bisogno è un Property Get o Function che possa accettare una stringa come primo argomento.

Per molti più dettagli e prove, vedi il mio post sul blog discutendo di questo: The Bang! (Operatore di esclamazione) in VBA

L’operatore bang ( ! ) È una scorciatoia per accedere ai membri di una Collection o di un altro object enumerabile, come ad esempio la proprietà Fields di un ADODB.Recordset .

Ad esempio, puoi creare una Collection e aggiungere alcuni elementi con chiave:

 Dim coll As Collection Set coll = New Collection coll.Add "First Item", "Item1" coll.Add "Second Item", "Item2" coll.Add "Third Item", "Item3" 

Puoi accedere a un elemento in questa raccolta tramite la sua chiave in tre modi:

  1. coll.Item("Item2")
    Questa è la forma più esplicita.

  2. coll("Item2")
    Questo funziona perché Item è il metodo predefinito della class Collection , quindi puoi ometterlo.

  3. coll!Item2
    Questa è una scorciatoia per entrambe le forms di cui sopra. In fase di esecuzione, VB6 prende il testo dopo il botto e lo passa come parametro al metodo Item .

La gente sembra renderla più complicata di quanto dovrebbe essere, ed è per questo che è difficile trovare una spiegazione semplice. Di solito le complicazioni o “ragioni per non usare l’operatore bang” derivano da un fraintendimento di quanto sia semplice. Quando qualcuno ha un problema con l’operatore bang, tende a biasimarlo invece della vera causa del problema che sta avendo, che è spesso più sottile.

Ad esempio, alcune persone consigliano di non utilizzare l’operatore bang per accedere ai controlli di un modulo. Quindi, Me.txtPhone è preferito su Me!txtPhone . Il “motivo” è considerato negativo poiché Me.txtPhone verrà controllato in fase di compilazione per correttezza, ma Me!txtPhone non lo farà.

Nel primo caso, se si digita il codice come Me.txtFone e non c’è alcun controllo con quel nome, il codice non verrà compilato. Nel secondo caso, se hai scritto Me!txtFone , non otterrai un errore di compilazione. Invece, il tuo codice esploderà con un errore di run-time se raggiunge la riga di codice che ha usato Me!txtFone .

Il problema con l’argomento contro l’operatore bang è che questo problema non ha nulla a che fare con l’operatore bang stesso. Si sta comportando esattamente come dovrebbe.

Quando aggiungi un controllo a un modulo, VB aggiunge automaticamente una proprietà al modulo con lo stesso nome del controllo aggiunto. Questa proprietà fa parte della class del modulo, quindi il compilatore può controllare gli errori di formattazione in fase di compilazione se si accede ai controlli utilizzando l’operatore punto (“.”) (E si può accedervi utilizzando l’operatore punto proprio perché VB ha creato un controllo denominato proprietà per te).

Poiché Me!ControlName è in realtà una Me.Controls("ControlName") per Me.Controls("ControlName") 1 , non dovrebbe sorprendere il fatto che non si ottengano controlli in fase di compilazione per l’errata digitazione del nome del controllo.

In altre parole, se l’operatore bang è “cattivo” e l’operatore punto è “buono”, allora si potrebbe pensare

 Me.Controls("ControlName") 

è meglio di

 Me!ControlName 

perché la prima versione utilizza un punto, ma in questo caso, il punto non è affatto migliore, dal momento che si sta ancora accedendo al nome del controllo tramite un parametro. È solo “migliore” quando esiste un modo alternativo di scrivere il codice in modo tale da ottenere un controllo in fase di compilazione. Questo è il caso dei controlli dovuti a VB che creano proprietà per ogni controllo per te, ed è per questo che Me.ControlName è talvolta raccomandato su Me!ControlName .


  1. Inizialmente avevo dichiarato che la proprietà Controls era la proprietà predefinita della class Form , ma David ha sottolineato nei commenti che Controls non è la proprietà predefinita di Form . La proprietà default effettiva restituisce una collezione che include il contenuto di Me.Controls , ed è per questo che il bang short-hand funziona ancora.

Trucchi per le coppie come addenda alle due risposte eccezionali già pubblicate:

Accesso ai campi del recordset nelle maschere e nei report
L’elemento predefinito degli oggetti Form in Access è un’unione della raccolta Controls della forma e della raccolta Fields del modulo recordset. Se il nome di un controllo è in conflitto con il nome di un campo, non sono sicuro di quale object venga effettivamente restituito. Poiché la proprietà predefinita sia di un campo sia di un controllo è il valore .Value , spesso è una “distinzione senza differenza”. In altre parole, normalmente non importa quale sia perché i valori del campo e del controllo sono spesso gli stessi.

Attenzione ai conflitti di denominazione!
Questa situazione è esacerbata dal progettista di Modulo e report di Access che ha eseguito il default per denominare i controlli associati allo stesso modo del campo recordset a cui sono associati. Ho adottato personalmente la convenzione di ridenominazione dei controlli con il loro prefisso del tipo di controllo (ad esempio, tbLastName per la casella di testo associata al campo LastName ).

I campi del recordset di report non ci sono!
Ho detto prima che l’object predefinito dell’object Form è una raccolta di controlli e campi. Tuttavia, l’elemento predefinito dell’object Report è solo la sua raccolta di controlli. Pertanto, se si desidera fare riferimento a un campo del recordset utilizzando l’operatore bang, è necessario includere tale campo come origine per un controllo associato (nascosto, se necessario).

Fai attenzione ai conflitti con le proprietà esplicite di form / report
Quando si aggiungono controlli a un modulo o report, Access crea automaticamente proprietà che fanno riferimento a questi controlli. Ad esempio, un controllo denominato tbLastName sarà disponibile dal modulo di codice di un modulo facendo riferimento a Me.tbLastName . Tuttavia, Access non creerà tale proprietà se è in conflitto con una forma esistente o una proprietà del report. Ad esempio, si supponga di aggiungere un controllo denominato Pagine. Facendo riferimento a Me.Pages nel modulo del codice del modulo verrà restituita la proprietà Pagine del modulo, non il controllo denominato “Pagine”.

In questo esempio, si poteva accedere esplicitamente al controllo “Pagine” usando Me.Controls("Pages") o implicitamente usando l’operatore bang, Me!Pages . Si noti, tuttavia, che l’utilizzo dell’operatore bang significa che Access potrebbe invece restituire un campo denominato “Pagine” se ne esiste uno nel set di record del modulo.

Che ne dici di .Value?
Anche se non menzionato esplicitamente nella domanda, questo argomento è apparso nei commenti sopra. La proprietà predefinita per gli oggetti Field e la maggior parte dei “dati associabili” ¹ Control objects è .Value . Poiché questa è la proprietà predefinita, viene generalmente considerata inutilmente dettagliata per includerla sempre esplicitamente. Pertanto, è prassi normale farlo:

 Dim EmployeeLastName As String EmployeeLastName = Me.tbLastName 

Invece di:

 EmployeeLastName = Me.tbLastName.Value 

Fai attenzione al sottile bug .Value quando digiti i dizionari
Ci sono alcuni casi in cui questa convenzione può causare bug sottili. Il più notevole – e, se la memoria serve, solo – uno in cui mi sono effettivamente imbattuto in pratica è quando si utilizza il valore di un campo / controllo come chiave del dizionario.

 Set EmployeePhoneNums = CreateObject("Scripting.Dictionary") Me.tbLastName.Value = "Jones" EmployeePhoneNums.Add Key:=Me.tbLastName, Item:="555-1234" Me.tbLastName.Value = "Smith" EmployeePhoneNums.Add Key:=Me.tbLastName, Item:="555-6789" 

Ci si aspetterebbe probabilmente che il codice precedente crei due voci nel dizionario EmployeePhoneNums . Invece, genera un errore sull’ultima riga perché stiamo cercando di aggiungere una chiave duplicata. Cioè, l’object di controllo tbLastName stesso è la chiave, non il valore del controllo. In questo contesto, il valore del controllo non ha nemmeno importanza.

In effetti, mi aspetto che l’indirizzo di memoria dell’object ( ObjPtr(Me.tbLastName) ) sia probabilmente utilizzato dietro le quinte per indicizzare il dizionario. Ho fatto un test rapido che sembra confermarlo.

 'Standard module: Public testDict As New Scripting.Dictionary Sub QuickTest() Dim key As Variant For Each key In testDict.Keys Debug.Print ObjPtr(key), testDict.Item(key) Next key End Sub 'Form module: Private Sub Form_Current() testDict(Me.tbLastName) = Me.tbLastName.Value Debug.Print ObjPtr(Me.tbLastName); "..."; Me.tbLastName End Sub 

Quando si esegue il codice sopra, viene aggiunto esattamente un elemento del dizionario ogni volta che il modulo viene chiuso e riaperto. Passare da record a record (e quindi causare più chiamate alla routine Form_Current) non aggiunge nuovi elementi di dizionario, poiché è l’object Control stesso che indicizza il dizionario e non il valore di Control.

Le mie raccomandazioni personali / convenzioni di codifica
Nel corso degli anni, ho adottato le seguenti pratiche, YMMV:

  • Prefix Form / Report controlla i nomi con gli indicatori del tipo di controllo (es. tbTextBox , lblLabel , ecc.)
  • Fare riferimento a Controlli Form / Report in codice usando Me. notazione (ad es. Me.tbLastName )
  • Evitare di creare campi tabella / query con nomi problematici in primo luogo
  • Me! notazione in caso di conflitti, ad esempio con applicazioni legacy (ad es. Me!Pages )
  • Includi controlli di report nascosti per accedere ai report dei valori dei campi di Recordset
  • Include esplicitamente .Value solo quando la situazione garantisce la verbosità aggiunta (ad esempio, le chiavi del dizionario)

¹ Che cos’è un controllo “associabile ai dati”?
Fondamentalmente, un controllo con una proprietà ControlSource , ad esempio un controllo TextBox o ComboBox. Un controllo non associabile potrebbe essere qualcosa come un’etichetta o un pulsante di comando. La proprietà predefinita sia di un controllo TextBox sia di ComboBox è .Value ; Labels e CommandButtons non hanno proprietà di default.