在阅读我正在记录的应用程序时,我在访问对象属性/方法等时遇到了一些 bang 表示法的示例,并且在其他地方,他们使用点表示法来实现看似相同的目的。
使用其中一种是否有区别或偏好?一些简单的谷歌搜索只能揭示有关该主题的有限信息,有些人实际上在相反的情况下使用它。也许某处的 MS 有一个编码标准部分表明了这种疯狂的方法?
尽管(以前)已接受此问题的答案,但 bang 实际上不是成员或集合访问运算符。它做了一件简单而具体的事情:bang 运算符提供对对象默认成员的后期绑定访问,方法是将 bang 运算符后面的文字名称作为字符串参数传递给该默认成员。
而已。对象不必是集合。它不必具有称为 的方法或属性Item
。它所需要的只是一个Property Get
or Function
,它可以接受一个字符串作为第一个参数。
有关更多详细信息和证明,请参阅我的博客文章讨论此问题:The Bang!VBA中的(感叹号)
bang 运算符 ( !
) 是访问 aCollection
或其他可枚举对象的成员的简写,例如Fields
an 的属性ADODB.Recordset
。
例如,您可以创建一个Collection
并向其添加一些键项:
Dim coll As Collection
Set coll = New Collection
coll.Add "First Item", "Item1"
coll.Add "Second Item", "Item2"
coll.Add "Third Item", "Item3"
您可以通过三种方式通过其键访问此集合中的项目:
coll.Item("Item2")
这是最明确的形式。
coll("Item2")
这是有效的,因为Item
它是Collection
类的默认方法,所以你可以省略它。
coll!Item2
这是上述两种形式的简写。在运行时,VB6 获取 bang 之后的文本并将其作为参数传递给Item
方法。
人们似乎把它弄得比它应该的更复杂,这就是为什么很难找到一个直截了当的解释。通常,并发症或“不使用 bang 运算符的原因”源于对它实际上有多简单的误解。当某人对 bang 运算符有问题时,他们倾向于归咎于它,而不是他们遇到问题的真正原因,这通常更微妙。
例如,有些人建议不要使用 bang 运算符来访问表单上的控件。因此,Me.txtPhone
优先于Me!txtPhone
。这被认为不好的“原因”是Me.txtPhone
在编译时检查正确性,但Me!txtPhone
不会。
在第一种情况下,如果您错误地将代码键入为Me.txtFone
并且没有使用该名称的控件,则您的代码将无法编译。在第二种情况下,如果您编写了Me!txtFone
,您将不会收到编译错误。相反,如果您的代码到达使用Me!txtFone
.
反对 bang 运算符的论点的问题在于,这个问题与 bang 运算符本身无关。它的行为完全符合它的预期。
当您向窗体添加控件时,VB 会自动向窗体添加一个与您添加的控件同名的属性。此属性是表单类的一部分,因此如果您使用点 (".") 运算符访问控件,编译器可以在编译时检查拼写错误(并且您可以使用点运算符访问它们,因为 VB 创建了一个命名控件财产给你)。
由于Me!ControlName
实际上是Me.Controls("ControlName")
1的简写,因此您没有得到任何针对错误输入控件名称的编译时检查也就不足为奇了。
换句话说,如果 bang 运算符是“坏”而点运算符是“好”,那么你可能会想
Me.Controls("ControlName")
好于
Me!ControlName
因为第一个版本使用点,但在这种情况下,点并没有更好,因为您仍然通过参数访问控件名称。只有当有另一种方法来编写代码以便您获得编译时检查时,它才会“更好”。由于 VB 会为您创建每个控件的属性,因此控件会出现这种情况,这就是为什么Me.ControlName
有时建议在Me!ControlName
.
Controls
属性是Form
该类的默认属性,但大卫在评论中指出这Controls
不是Form
. 实际的默认属性返回一个包含内容的集合Me.Controls
,这就是 bang 简写仍然有效的原因。有几个问题可以作为已经发布的两个特殊答案的补充:
访问表单与报告中的记录集字段
Access 中表单对象的默认项是表单的 Controls 集合和表单记录集的 Fields 集合的联合。如果控件的名称与字段的名称冲突,我不确定实际返回的是哪个对象。由于字段和控件的默认属性都是它们的.Value
,因此通常是“没有区别的区别”。换句话说,人们通常不关心它是什么,因为字段和控件的值通常是相同的。
小心命名冲突!
Access 的窗体和报表设计器默认将绑定控件命名为与它们绑定到的记录集字段相同的名称,从而加剧了这种情况。我个人采用了使用控件类型前缀重命名控件的约定(例如,tbLastName
对于绑定到LastName字段的文本框)。
报告记录集字段不存在!
我之前说过,Form 对象的默认项是控件和字段的集合。但是,报表对象的默认项只是它的控件集合。因此,如果想使用 bang 运算符引用记录集字段,则需要将该字段作为(隐藏,如果需要)绑定控件的源。
当心与显式表单/报表属性冲突
当向表单或报表添加控件时,Access 会自动创建引用这些控件的属性。例如,一个名为的控件tbLastName
可以通过引用从表单的代码模块获得Me.tbLastName
。但是,如果 Access 与现有表单或报表属性冲突,则不会创建此类属性。例如,假设添加了一个名为 Pages 的控件。在表单的代码模块中引用Me.Pages
将返回表单的Pages属性,而不是名为“Pages”的控件。
Me.Controls("Pages")
在此示例中,可以使用 bang 运算符显式或隐式访问“页面”控件Me!Pages
。但是请注意,使用 bang 运算符意味着如果表单的记录集中存在一个名为“Pages”的字段,Access 可能会改为返回一个名为“Pages”的字段。
.Value 呢?
虽然问题中没有明确提到,但这个话题出现在上面的评论中。Field 对象和大多数“数据绑定”¹ Control 对象的默认属性是.Value
. 由于这是默认属性,.Value
当返回对象本身没有意义时,VBA 将隐式返回该属性的值。因此,这样做是常见的做法......
Dim EmployeeLastName As String
EmployeeLastName = Me.tbLastName
...而不是这个...
EmployeeLastName = Me.tbLastName.Value
上述两个语句产生相同的结果,因为EmployeeLastName
是一个字符串。
键入字典时要小心细微的 .Value 错误 在
某些情况下,这种约定可能会导致细微的错误。最值得注意的——如果没记错的话,只有——我在实践中实际遇到的一个是使用字段/控件的值作为字典键时。
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"
人们可能会期望上面的代码在EmployeePhoneNums
字典中创建两个条目。相反,它会在最后一行引发错误,因为我们正在尝试添加重复键。也就是说,tbLastName
Control 对象本身是键,而不是控件的值。在这种情况下,控件的值甚至无关紧要。
事实上,我希望对象的内存地址 ( ObjPtr(Me.tbLastName)
) 可能是在幕后用来索引字典的东西。我做了一个快速测试,似乎证明了这一点。
'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
运行上述代码时,每次关闭并重新打开表单时,都会添加一个字典项。从一个记录移动到另一个记录(并因此导致对 Form_Current 例程的多次调用)不会添加新的字典项,因为它是控件对象本身索引字典,而不是控件的值。
我的个人建议/编码约定
多年来,我采用了以下做法,YMMV:
tbTextBox
,lblLabel
等)Me.
符号(例如,Me.tbLastName
)Me!
符号,例如与遗留应用程序(例如,Me!Pages
).Value
仅当情况需要增加详细程度时才明确包括(例如,字典键)¹什么是“数据可绑定”控件?
基本上,一个具有ControlSource
属性的控件,例如 TextBox 或 ComboBox。不可绑定控件类似于标签或命令按钮。TextBox 和 ComboBox 的默认属性是.Value
; 标签和命令按钮没有默认属性。