【发布时间】:2011-02-24 19:43:09
【问题描述】:
在阅读我是 documenting 的应用程序时,我在访问对象属性/方法等时遇到了一些 bang 表示法的示例,并且在其他地方,他们使用点表示法来实现看似相同的目的。
使用其中一种是否有区别或偏好?一些简单的谷歌搜索只能揭示有关该主题的有限信息,有些人实际上在相反的情况下使用它。也许某处的 MS 有一个编码标准部分表明了疯狂的方法?
【问题讨论】:
标签: ms-access vba vb6 notation
在阅读我是 documenting 的应用程序时,我在访问对象属性/方法等时遇到了一些 bang 表示法的示例,并且在其他地方,他们使用点表示法来实现看似相同的目的。
使用其中一种是否有区别或偏好?一些简单的谷歌搜索只能揭示有关该主题的有限信息,有些人实际上在相反的情况下使用它。也许某处的 MS 有一个编码标准部分表明了疯狂的方法?
【问题讨论】:
标签: ms-access vba vb6 notation
尽管(以前)已接受此问题的答案,但 bang 实际上不是成员或集合访问运算符。它做了一件简单而具体的事情:bang 运算符提供对对象默认成员的后期绑定访问,方法是将 bang 运算符后面的文字名称作为字符串参数传递给该默认成员。
就是这样。对象不必是集合。它不必具有名为Item 的方法或属性。它所需要的只是一个Property Get 或Function,它们可以接受一个字符串作为第一个参数。
有关更多详细信息和证明,请参阅我的博客文章讨论此问题:The Bang! (Exclamation Operator) in VBA
【讨论】:
bang 运算符 (!) 是访问 Collection 或其他可枚举对象(例如 ADODB.Recordset 的 Fields 属性)成员的简写。
例如,您可以创建一个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 类的默认属性,但David 在cmets 中指出Controls 不是Form 的默认属性。实际的默认属性返回一个集合,该集合包含Me.Controls 的内容,这就是 bang 简写仍然有效的原因。【讨论】:
Form 类没有 Fields 集合。我将编辑我的答案以澄清。但是现在您提到它,从技术上讲,Controls 也不是 VB6 中Form 的默认属性:有一个名为_Default 的隐藏属性被列为该类的默认成员。不确定它是 VB6 中多个集合的联合还是仅返回 Controls。不过很好抓。现在你让我想解开这个谜团:-)
rs.Fields("ColumnName").Value,因为rs!ColumnName“并不总是有效”,但正如你所指出的,任何问题都是由于没有明确使用Value属性,在那些它确实有所作为的情况。 rs!ColumnName.Value 本来可以很容易地解决这个问题,所以这只是另一个错误的情况,真正的问题是 VB6 如何以及何时评估默认属性。
作为已发布的两个特殊答案的补充,有几个陷阱:
访问表单和报告中的记录集字段
Access 中表单对象的默认项是表单的 Controls 集合和表单记录集的 Fields 集合的并集。如果控件的名称与字段的名称冲突,我不确定实际返回的是哪个对象。由于字段和控件的默认属性都是它们的.Value,因此通常是“没有区别的区别”。换句话说,人们通常不关心它是什么,因为字段和控件的值通常是相同的。
小心命名冲突!
Access 的窗体和报表设计器默认将绑定控件命名为与它们绑定的记录集字段相同,从而加剧了这种情况。我个人采用了使用控件类型前缀重命名控件的约定(例如,tbLastName 用于绑定到 LastName 字段的文本框)。
报告记录集字段不存在!
我之前说过,Form 对象的默认项是控件和字段的集合。但是,报表对象的默认项只是它的控件集合。因此,如果想使用 bang 运算符引用记录集字段,则需要将该字段作为(隐藏,如果需要)绑定控件的源。
注意与显式表单/报告属性的冲突
当向窗体或报表添加控件时,Access 会自动创建引用这些控件的属性。例如,一个名为tbLastName 的控件可以通过引用Me.tbLastName 从表单的代码模块中获得。但是,如果 Access 与现有表单或报表属性冲突,则不会创建此类属性。例如,假设添加了一个名为 Pages 的控件。在表单的代码模块中引用Me.Pages 将返回表单的Pages 属性,而不是名为“Pages”的控件。
在本例中,可以使用Me.Controls("Pages") 显式访问“Pages”控件,也可以使用 bang 运算符Me!Pages 隐式访问“Pages”控件。但请注意,使用 bang 运算符意味着如果表单的记录集中存在一个名为“Pages”的字段,Access 可能会改为返回一个名为“Pages”的字段。
.Value 呢?
尽管问题中没有明确提及,但该主题出现在上述 cmets 中。 Field 对象和大多数“数据绑定”¹控件对象的默认 属性 是 .Value。由于这是默认属性,当返回对象本身没有意义时,VBA 将隐式返回 .Value 属性的值。因此,这样做是常见的做法......
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 字典中创建两个条目。相反,它会在最后一行引发错误,因为我们正在尝试添加重复键。也就是说,tbLastNameControl 对象本身是键,而不是控件的值。在这种情况下,控件的值甚至无关紧要。
事实上,我预计对象的内存地址 (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;标签和命令按钮没有默认属性。
【讨论】: