Notation Bang et Notation par points en VBA et MS-Access
En parcourant une application que je documente , j'ai rencontré quelques exemples de notation bang dans l'accès aux propriétés/méthodes de l'objet, etc. et dans d'autres endroits, ils utilisent la notation par points pour ce qui semble être le même but.
Y a-t-il une différence ou une préférence pour utiliser l'un ou l'autre? Certains googling simple ne révèle que des informations limitées sur le sujet avec certaines personnes qui l'utilisent réellement dans les cas opposés. Peut-être y a-t-il une section sur les normes de codage de MS quelque part qui indique la méthode de la folie?
3 réponses
Malgré la réponse (anciennement) acceptée à cette question, le bang n'est en fait pas un membre ou un opérateur d'accès à la collection. Il fait une chose simple et spécifique: l'opérateur Bang fournit un accès lié tardivement au membre par défaut d'un objet, en passant le nom littéral suivant l'opérateur bang comme argument de chaîne à ce membre par défaut.
C'est ça. L'objet ne doit pas nécessairement être une collection. Il n'a pas besoin d'avoir une méthode ou une propriété appelée Item
. Tout ce qu'il faut est un Property Get
ou Function
qui peut accepter une chaîne comme premier argument.
Pour beaucoup plus de détails et de preuves, voir mon article de blog en discutant: Le Bang! (Opérateur d'Exclamation) dans VBA
Bang opérateur (!
) est un raccourci pour accéder aux membres d'une Collection
ou autre objet énumérable, tels que les Fields
propriété d'un ADODB.Recordset
.
Par exemple, vous pouvez créer un Collection
et y ajouter quelques éléments clés:
Dim coll As Collection
Set coll = New Collection
coll.Add "First Item", "Item1"
coll.Add "Second Item", "Item2"
coll.Add "Third Item", "Item3"
, Vous pouvez accéder à un élément de cette collection par sa clé de trois façons:
coll.Item("Item2")
C'est le plus explicite.coll("Item2")
Cela fonctionne parce queItem
est la méthode par défaut de la classeCollection
, donc vous pouvez l'omettre.coll!Item2
Il s'agit d'une main courte pour les deux formes ci-dessus. Au moment de l'exécution, VB6 prend le texte après le bang et le passe en paramètre à la méthodeItem
.
Les gens semblent rendre cela plus compliqué qu'il ne devrait l'être, c'est pourquoi il est difficile de trouver une explication simple. Habituellement, les complications ou les "raisons de ne pas utiliser l'opérateur bang" découlent d'une mauvaise compréhension de la simplicité de son fonctionnement. Quand quelqu'un a un problème avec l'opérateur bang, ils ont tendance à le blâmer au lieu de la véritable cause du problème qu'ils ont, ce qui est souvent plus subtil.
Par exemple, certaines personnes recommandent de ne pas utiliser l'opérateur bang pour accéder aux contrôles d'un formulaire. Ainsi, {[14] } est préféré à Me!txtPhone
. La "raison" pour laquelle cela est considéré comme mauvais est que Me.txtPhone
sera vérifié à la compilation pour l'exactitude, mais Me!txtPhone
ne le sera pas.
Dans le premier cas, si vous confondez le code avec Me.txtFone
et qu'il n'y a pas de contrôle avec ce nom, votre code ne sera pas compilé. Dans le second cas, si vous avez écrit Me!txtFone
, Vous n'obtiendrez pas d'erreur de compilation. Au lieu de cela, votre code va exploser avec une erreur d'exécution s'il atteint la ligne de code utilisée Me!txtFone
.
Le problème avec l'argument contre l'opérateur bang est que ce problème n'a rien à voir avec l'opérateur bang lui-même. Il se comporte exactement comme il est censé le faire.
Lorsque vous ajoutez un contrôle à un formulaire, VB ajoute automatiquement une propriété à votre formulaire avec le même nom comme le contrôle que vous avez ajouté. Cette propriété fait partie de la classe du formulaire, de sorte que le compilateur peut vérifier les fautes de frappe au moment de la compilation si vous accédez aux contrôles en utilisant le point (".") opérateur (et vous pouvez y accéder en utilisant l'opérateur dot précisément parce que VB a créé une propriété de contrôle nommée pour vous).
Puisque Me!ControlName
est en fait une main courte pour Me.Controls("ControlName")
1, Il ne devrait pas être surprenant que vous n'obteniez aucune vérification à la compilation contre une erreur de frappe du nom du contrôle.
Autrement dit, si l'opérateur bang est "mauvais" et l'opérateur dot est "bon", alors vous pourriez penser
Me.Controls("ControlName")
Est meilleur que
Me!ControlName
Parce que la première version utilise un point, mais dans ce cas, le point n'est pas meilleur du tout, puisque vous accédez toujours au nom du contrôle via un paramètre. C'est seulement "mieux" quand il y a une autre façon d'écrire le code de sorte que vous obtiendrez une vérification à la compilation. Cela arrive à être le cas avec les contrôles en raison de la création de propriétés VB pour chaque contrôle pour vous, et c'est pourquoi Me.ControlName
est parfois recommandé sur Me!ControlName
.
- j'avais initialement déclaré que la propriété
Controls
était la propriété par défaut de la classeForm
, Mais David a souligné dans les commentaires queControls
n'est pas la propriété par défaut deForm
. La propriété par défaut réelle renvoie une collection qui inclut le contenu deMe.Controls
, c'est pourquoi le bang short-hand fonctionne toujours.
Quelques gotchas pour servir d'addenda aux deux réponses exceptionnelles déjà postées:
Accès aux champs recordset dans les formulaires par rapport aux rapports
L'élément par défaut des objets de formulaire dans Access est une union de la collection Controls du formulaire et de la collection Fields du jeu d'enregistrements de formulaire. Si le nom d'un contrôle est en conflit avec le nom d'un champ, Je ne suis pas sûr de l'objet qui est réellement retourné. Comme la propriété par défaut d'un champ et d'un contrôle est leur .Value
, il s'agit souvent d'un "distinction sans différence."En d'autres termes, on ne se soucie normalement pas de ce que c'est parce que les valeurs du champ et du contrôle sont souvent les mêmes.
Méfiez-vous des conflits de noms!
Cette situation est exacerbée par le concepteur de formulaires et de rapports D'Access qui utilise par défaut le nom des contrôles liés comme le champ recordset auquel ils sont liés. J'ai personnellement adopté la convention de renommer les contrôles avec leur préfixe de type de contrôle (par exemple, tbLastName
pour la zone de texte lié au champ LastName).
Report Recordset champs ne sont pas là!
J'ai dit plus tôt L'élément par défaut de L'objet Form est une collection de contrôles et de champs. Cependant, l'élément par défaut de L'objet rapport est uniquement sa collection de contrôles. Donc, si l'on veut faire référence à un champ recordset en utilisant l'opérateur bang, il faut inclure ce champ comme source pour un contrôle lié (caché, si désiré).
Méfiez-vous des conflits avec le formulaire/rapport explicite propriétés
Lorsqu'on ajoute des contrôles à un formulaire ou à un rapport, Access crée automatiquement des propriétés qui font référence à ces contrôles. Par exemple, un contrôle nommé tbLastName
serait disponible à partir du module de code d'un formulaire en se référant à Me.tbLastName
. Cependant, Access ne créera pas une telle propriété si elle est en conflit avec une propriété de formulaire ou de rapport existante. Par exemple, supposons que l'on ajoute un contrôle nommé Pages. Se référer à Me.Pages
dans le module de code du formulaire renverra la propriété Pages du formulaire, pas le contrôle nommé "Pages".
Dans cet exemple, on peut accéder au contrôle" Pages " explicitement en utilisant {[9] } ou implicitement en utilisant l'opérateur bang, Me!Pages
. Sachez cependant que l'utilisation de l'opérateur bang signifie que Access peut renvoyer un champ nommé "Pages" s'il existe dans le jeu d'enregistrements du formulaire.
A propos de quoi .De la valeur?
Bien que pas explicitement mentionné dans la question, ce sujet est apparu dans les commentaires ci-dessus. La propriété par défaut pour Les objets de champ et la plupart des objets de contrôle "data-bindable"1 sont .Value
. Comme il s'agit de la propriété par défaut, il est généralement considéré inutilement verbeux de toujours l'inclure explicitement. Ainsi, c'est la pratique standard de faire ceci:
Dim EmployeeLastName As String
EmployeeLastName = Me.tbLastName
Au lieu de:
EmployeeLastName = Me.tbLastName.Value
Méfiez - vous du subtil .Bug de valeur lors de la saisie des dictionnaires
Il y a des cas où cette convention peut causer des bugs subtils. Le plus notable-et, si la mémoire sert, seulement-un que j'ai rencontré en fait la pratique consiste à utiliser la valeur D'un champ / contrôle comme clé de dictionnaire.
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"
On s'attendrait probablement à ce que le code ci-dessus crée deux entrées dans le dictionnaire EmployeePhoneNums
. Au lieu de cela, il génère une erreur sur la dernière ligne car nous essayons d'ajouter une clé en double. C'est-à-dire que l'objet de contrôle tbLastName
est lui-même la clé, pas la valeur du contrôle. Dans ce contexte, la valeur du contrôle n'a même pas d'importance.
En fait, je m'attends à ce que l'adresse mémoire de l'objet (ObjPtr(Me.tbLastName)
) soit probablement ce qui est utilisé dans les coulisses pour indexer le dictionnaire. J'ai fait un test rapide qui semble confirmer cela.
'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
Lors de l'exécution du code ci-dessus, exactement un élément de dictionnaire est ajouté chaque fois que le formulaire est fermé et rouvert. Se déplacer d'un enregistrement à l'autre (et provoquant ainsi plusieurs appels à la routine Form_Current) n'ajoute pas de nouveaux éléments de dictionnaire, car c'est l'objet de contrôle lui-même qui indexe le dictionnaire, et non la valeur du contrôle.
Mon recommandations personnelles/conventions de codage
Au fil des ans, j'ai adopté les pratiques suivantes, YMMV:
- préfixe des noms de contrôle de formulaire/rapport avec des indicateurs de type de contrôle (par exemple,
tbTextBox
,lblLabel
, etc.) - se référer aux contrôles de formulaire/rapport dans le code en utilisant la notation
Me.
(par exemple,Me.tbLastName
) - évitez de créer des champs de table / requête avec des noms problématiques en premier lieu
- utilisez la notation
Me!
lorsqu'il y a des conflits, comme avec legacy applications (par exemple,Me!Pages
) - inclure des contrôles de rapport masqués pour accéder aux valeurs de champ du jeu d'enregistrements de rapport
- inclure explicitement {[4] } uniquement lorsque la situation justifie la verbosité ajoutée (par exemple, les clés du dictionnaire)
1 qu'est-ce qu'un contrôle" lié aux données"?
fondamentalement, un contrôle avec une propriété ControlSource
, comme une zone de texte ou une liste déroulante. Un contrôle non-bindable serait quelque chose comme une étiquette ou un CommandButton. La propriété par défaut des deux une zone de texte et une liste déroulante sont .Value
; Les étiquettes et les boutons de commande n'ont pas de propriété par défaut.