Notación de Bang y notación de puntos en VBA y MS-Access

Al leer detenidamente una aplicación que estoy documentando , me encontré con algunos ejemplos de notación bang al acceder a propiedades / métodos de objetos, etc. y en otros lugares usan notación de puntos para lo que parece ser el mismo propósito.

¿Hay alguna diferencia o preferencia al uso de uno u otro? Algunos simples Google solo revelan información limitada sobre el tema, y ​​algunas personas lo usan en casos opuestos. ¿Tal vez hay una sección de estándares de encoding de MS en alguna parte que indique el método de la locura?

A pesar de la respuesta (anteriormente) aceptada a esta pregunta, el bang no es en realidad un miembro o un operador de acceso a la colección. Hace una cosa simple y específica: el operador bang proporciona acceso limitado al miembro predeterminado de un objeto, pasando el nombre literal que sigue al operador bang como un argumento de cadena a ese miembro predeterminado.

Eso es. El objeto no tiene que ser una colección. No tiene que tener un método o propiedad llamado Item . Todo lo que necesita es un Property Get o Function que puede aceptar una cadena como primer argumento.

Para obtener más detalles y pruebas, consulte la publicación de mi blog que analiza esto: ¡ The Bang! (Operador de exclamación) en VBA

El operador bang ( ! ) Es una abreviatura para acceder a los miembros de una Collection u otro objeto enumerable, como la propiedad Fields de un ADODB.Recordset .

Por ejemplo, puede crear una Collection y agregarle algunos elementos clave:

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

Puede acceder a un elemento en esta colección por su clave de tres maneras:

  1. coll.Item("Item2")
    Esta es la forma más explícita.

  2. coll("Item2")
    Esto funciona porque Item es el método predeterminado de la clase Collection , por lo que puede omitirlo.

  3. coll!Item2
    Esto es abreviado para las dos formas anteriores. En tiempo de ejecución, VB6 toma el texto después del bang y lo pasa como un parámetro al método Item .

La gente parece hacer esto más complicado de lo que debería ser, por lo que es difícil encontrar una explicación directa. Por lo general, las complicaciones o “razones para no utilizar el operador de bang” derivan de una mala comprensión de lo simple que es en realidad. Cuando alguien tiene un problema con el operador de bang, tienden a culparlo en lugar de la verdadera causa del problema que tienen, que a menudo es más sutil.

Por ejemplo, algunas personas recomiendan no usar el operador bang para acceder a los controles en un formulario. Por lo tanto, Me.txtPhone es preferido sobre Me!txtPhone . La “razón” de que esto se vea como malo es que Me.txtPhone se comprobará en tiempo de comstackción para que sea correcto, pero Me!txtPhone no lo hará.

En el primer caso, si escribe mal el código como Me.txtFone y no hay control con ese nombre, su código no se comstackrá. En el segundo caso, si escribió Me!txtFone , no obtendrá un error de comstackción. En cambio, su código explotará con un error en tiempo de ejecución si alcanza la línea de código que usó Me!txtFone .

El problema con el argumento en contra del operador de bang es que este problema no tiene nada que ver con el propio operador de bang. Se está comportando exactamente de la manera en que se supone.

Cuando agrega un control a un formulario, VB agrega automáticamente una propiedad a su formulario con el mismo nombre que el control que agregó. Esta propiedad es parte de la clase del formulario, por lo que el comstackdor puede verificar los errores tipográficos en tiempo de comstackción si accedes a los controles usando el operador de puntos (“.”) (Y puedes acceder a ellos usando el operador de puntos precisamente porque VB creó un control nombrado propiedad para usted).

Dado que Me!ControlName es en realidad short-hand para Me.Controls("ControlName") 1 , no debe sorprender que no se obtengan comprobaciones en tiempo de comstackción contra el error al escribir el nombre del control.

Dicho de otra manera, si el operador de bang es “malo” y el operador de punto es “bueno”, entonces podrías pensar

 Me.Controls("ControlName") 

es mejor que

 Me!ControlName 

porque la primera versión usa un punto, pero en este caso, el punto no es mejor en absoluto, ya que todavía está accediendo al nombre del control a través de un parámetro. Solo es “mejor” cuando hay una forma alternativa de escribir el código, de modo que obtenga una verificación en tiempo de comstackción. Este es el caso de los controles debido a las propiedades de creación de VB para cada control, y esta es la razón por la que Me.ControlName veces se recomienda sobre Me!ControlName .


  1. Originalmente, había declarado que la propiedad Controls era la propiedad predeterminada de la clase Form , pero David señaló en los comentarios que Controls no es la propiedad predeterminada de Form . La propiedad predeterminada real devuelve una colección que incluye el contenido de Me.Controls , por lo que la mano corta de bang aún funciona.

Complicaciones de pareja para servir como una adición a las dos respuestas excepcionales ya publicadas:

Acceder a los campos del conjunto de registros en formularios frente a informes
El elemento predeterminado de Objetos de formulario en Access es una unión de la colección Controles del formulario y la colección de Campos del conjunto de registros de formulario. Si el nombre de un control entra en conflicto con el nombre de un campo, no estoy seguro de qué objeto se devuelve realmente. Como la propiedad predeterminada de un campo y un control es su .Value , a menudo es una “distinción sin diferencia”. En otras palabras, a uno normalmente no le importa cuál es porque los valores del campo y el control son a menudo los mismos.

¡Cuidado con los conflictos de nombres!
Esta situación se ve agravada por el hecho de que el Diseñador de Informes y Formularios de Ingreso elije por defecto el nombre de los controles vinculados de la misma forma que el campo de conjunto de registros al cual están vinculados. Personalmente, he adoptado la convención de cambiar el nombre de los controles con su prefijo de tipo de control (por ejemplo, tbLastName para el cuadro de texto vinculado al campo Apellido ).

¡Los campos del conjunto de registros de informes no están allí!
Dije antes que el elemento predeterminado del objeto Formulario es una colección de Controles y Campos. Sin embargo, el elemento predeterminado del objeto Informe es solo su colección de Controles. Entonces, si uno quiere referirse a un campo de conjunto de registros usando el operador de bang, uno necesita incluir ese campo como la fuente de un control encuadernado (oculto, si se desea).

Tenga cuidado con los conflictos con las propiedades explícitas de formulario / informe
Cuando uno agrega controles a un formulario o informe, Access crea automáticamente propiedades que hacen referencia a estos controles. Por ejemplo, un control denominado tbLastName estaría disponible desde el módulo de código de un formulario haciendo referencia a Me.tbLastName . Sin embargo, Access no creará dicha propiedad si entra en conflicto con un formulario existente o una propiedad de informe. Por ejemplo, supongamos que uno agrega un control llamado Pages. Refiriéndome a Me.Pages en el módulo de código del formulario devolverá la propiedad Páginas del formulario, no el control llamado “Páginas”.

En este ejemplo, se puede acceder al control “Páginas” explícitamente utilizando Me.Controls("Pages") o utilizando implícitamente el operador bang, Me!Pages . Tenga en cuenta, sin embargo, que el uso del operador bang significa que Access podría devolver un campo llamado “Páginas” si existe en el conjunto de registros del formulario.

¿Qué hay de .Value?
Aunque no se menciona explícitamente en la pregunta, este tema apareció en los comentarios anteriores. La propiedad predeterminada para los objetos Field y la mayoría de los objetos de control .Value Control de datos es .Value . Dado que esta es la propiedad predeterminada, generalmente se considera innecesariamente detallado incluirla siempre explícitamente. Por lo tanto, es una práctica estándar para hacer esto:

 Dim EmployeeLastName As String EmployeeLastName = Me.tbLastName 

En lugar de:

 EmployeeLastName = Me.tbLastName.Value 

Tenga cuidado con el error sutil de .Value al teclear diccionarios
Hay algunos casos donde esta convención puede causar errores sutiles. Lo más notable (y, si solo sirve memoria), uno con el que me he encontrado en la práctica es cuando uso el valor de un campo / control como una tecla del diccionario.

 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" 

Es de esperar que el código anterior cree dos entradas en el diccionario EmployeePhoneNums . En su lugar, arroja un error en la última línea porque estamos tratando de agregar una clave duplicada. Es decir, el objeto Control tbLastName sí mismo es la clave, no el valor del control. En este contexto, el valor del control ni siquiera importa.

De hecho, espero que la dirección de memoria del objeto ( ObjPtr(Me.tbLastName) ) sea la que se utiliza detrás de escena para indexar el diccionario. Hice una prueba rápida que parece confirmar esto.

 '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 

Al ejecutar el código anterior, se agrega exactamente un elemento del diccionario cada vez que se cierra y se vuelve a abrir el formulario. Pasar de registro a registro (y así causar múltiples llamadas a la rutina Form_Current) no agrega nuevos elementos del diccionario, porque es el objeto Control el que indexa el diccionario, y no el valor del Control.

Mis recomendaciones personales / convenciones de encoding
Con los años, he adoptado las siguientes prácticas, YMMV:

  • Prefijo nombres de control de formulario / informe con indicadores de tipo de control (por ejemplo, tbTextBox , lblLabel , etc.)
  • Consulte los controles de Formulario / Informe en el código que usa Me. notación (p. ej., Me.tbLastName )
  • Evite crear campos de tabla / consulta con nombres problemáticos en primer lugar
  • Me! notación cuando hay conflictos, como con aplicaciones heredadas (p. ej., Me!Pages )
  • Incluya controles de informe ocultos para obtener acceso a los valores de campo del conjunto de registros
  • Incluya explícitamente .Value solo cuando la situación amerite la verbosidad agregada (p. Ej., Las teclas del Diccionario)

¹ ¿Qué es un control “vinculable a datos”?
Básicamente, un control con una propiedad ControlSource , como un TextBox o ComboBox. Un control no vinculable sería algo así como una etiqueta o CommandButton. La propiedad predeterminada de un TextBox y un ComboBox es .Value ; Las tags y CommandButtons no tienen propiedad predeterminada.