VBA和MS-Access中的Bang符号和点表示法

Nit*_*ist 25 vb6 ms-access vba notation

在阅读我正在记录的应用程序时,我在访问对象属性/方法等时遇到了一些bang符号示例,而在其他地方,他们使用点符号来表示同样的目的.

使用其中一个是否存在差异或偏好?一些简单的谷歌搜索只显示有关该主题的有限信息,有些人实际上在相反的情况下使用它.也许MS的编码标准部分可以指出疯狂的方法?

Jos*_*nig 35

尽管(以前)接受了这个问题的答案,但实际上并不是成员或集合访问运营商.它做了一个简单而具体的事情:bang运算符通过将bang运算符后面的文字名称作为字符串参数传递给该默认成员,提供对对象的默认成员的后期绑定访问.

而已.该对象不必是一个集合.它不必具有调用的方法或属性Item.它需要的只是一个Property Get或者Function可以接受一个字符串作为第一个参数.

有关更多细节和证据,请参阅我的博客文章讨论:Bang!(VBA中的感叹号操作员)

  • 好的,好的,约书亚.你的博客文章是关于爆炸操作员的知识的非常好的升华.我必须找到这个困难的方法,因为MS文档在这个主题上是如此糟糕.我希望我15年前读过这一切! (2认同)
  • @Nitrodist:我也很确定约书亚是对的.我的回答没有说​​清楚,也没有提出同样的观点;-).例如,请参阅我的答案中的脚注.我的答案中的第一句话确实不是措辞的最佳选择 - 我说的是一个常见的用例,但是看起来我似乎正在定义bang运算符的目的/行为,这不是故意的.尽管如此,我的回答只是_suggests_它调用了对象的默认成员,而没有实际说出来,所以这个答案肯定比我的更重要. (2认同)

Mik*_*oss 27

bang operator(!)是访问一个Collection或其他可枚举对象的成员的简写,例如a的Fields属性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"
Run Code Online (Sandbox Code Playgroud)

您可以通过三种方式通过其键访问此集合中的项目:

  1. coll.Item("Item2")
    这是最明确的形式.

  2. coll("Item2")
    这是因为Item是类的默认方法Collection,所以你可以省略它.

  3. coll!Item2
    这是上述两种形式的简写.在运行时,VB6在爆炸后获取文本并将其作为参数传递给Item方法.

人们似乎比这更复杂,这就是为什么很难找到一个直截了当的解释.通常,并发症或"不使用爆炸操作员的原因"源于对其实际简单性的误解.当某人对爆炸操作员有问题时,他们倾向于责怪它而不是他们所遇到的问题的真正原因,这通常更为微妙.

例如,有些人建议不要使用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,它不应该被suprising你没有得到任何编译时检查针对错误输入控件名称.

换句话说,如果爆炸操作员是"坏"而点操作员是"好",那么你可能会想

Me.Controls("ControlName")
Run Code Online (Sandbox Code Playgroud)

比...更好

Me!ControlName
Run Code Online (Sandbox Code Playgroud)

因为第一个版本使用点,但在这种情况下,点完全没有任何好处,因为您仍然通过参数访问控件名称.当有另一种编写代码的方式时,它只会"更好",以便您获得编译时检查.由于VB为您创建了每个控件的属性,因此控件就是这种情况,这就是为什么Me.ControlName有时会推荐它Me!ControlName.


  1. 我原先声明该Controls属性是Form该类的默认属性,但David在注释中指出这Controls不是默认属性Form.实际的默认属性返回一个包含内容的集合Me.Controls,这就是bang short-hand仍然有效的原因.

  • Me.Controls*不是*表单对象的默认"属性".表单或报表中Me的默认*集合*是Controls和Fields集合的并集.在作为独立类模块中,没有默认集合.否则,很好的答案,概括了我不久前发布的答案,我懒得抬头! (2认同)

mwo*_*e02 5

几个问题可以作为已经发布的两个特殊答案的附录:

访问窗体与报表中
记录集字段Access 中窗体对象的默认项是窗体的 Controls 集合和窗体记录集的 Fields 集合的联合。如果控件名称与字段名称冲突,我不确定实际返回的是哪个对象。由于字段和控件的默认属性都是它们的.Value,因此通常是“没有区别的区别”。换句话说,人们通常不关心它是哪个,因为字段和控件的值通常是相同的。

小心命名冲突!
Access 的窗体和报表设计器默认将绑定控件命名为与其绑定的记录集字段相同的名称,从而加剧了这种情况。我个人采用了重命名控件及其控件类型前缀的约定(例如,tbLastName对于绑定到LastName字段的文本框)。

报告记录集字段不存在!
我之前说过 Form 对象的默认项是 Controls 和 Fields 的集合。但是,Report 对象的默认项只是它的控件集合。因此,如果要使用 bang 运算符引用记录集字段,则需要将该字段作为(隐藏的,如果需要)绑定控件的源包括在内。

注意与显式窗体/报表属性的冲突
当向窗体或报表添加控件时,Access 会自动创建引用这些控件的属性。例如,tbLastName通过引用Me.tbLastName. 但是,如果与现有窗体或报表属性冲突,Access 将不会创建此类属性。例如,假设添加了一个名为 Pages 的控件。Me.Pages在表单的代码模块中引用将返回表单的Pages属性,而不是名为“Pages”的控件。

在此示例中,可以Me.Controls("Pages")使用 bang 运算符显式或隐式地访问“Pages”控件Me!Pages。但是请注意,使用 bang 运算符意味着如果表单的记录集中存在名为“Pages”的字段,则 Access 可能会返回该字段。

.Value 呢?
尽管在问题中没有明确提及,但在上述评论中提到了这个话题。Field 对象和大多数“数据可绑定”¹ Control 对象的默认属性.Value. 由于这是默认属性,.Value当返回对象本身没有意义时,VBA 将隐式返回该属性的值。因此,通常的做法是这样做......

Dim EmployeeLastName As String
EmployeeLastName = Me.tbLastName
Run Code Online (Sandbox Code Playgroud)

......而不是这个......

EmployeeLastName = Me.tbLastName.Value
Run Code Online (Sandbox Code Playgroud)

以上两个语句产生相同的结果,因为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"
Run Code Online (Sandbox Code Playgroud)

人们可能会期望上面的代码在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
Run Code Online (Sandbox Code Playgroud)

运行上述代码时,每次关闭并重新打开表单时,都会添加一个字典项。从记录移动到记录(从而导致多次调用 Form_Current 例程)不会添加新的字典项,因为它是 Control 对象本身索引字典,而不是 Control 的值。

我的个人建议/编码约定
多年来,我采用了以下实践,YMMV:

  • 与控制型指标前缀窗体/报表控件名称(如tbTextBoxlblLabel等)
  • 使用Me.符号(例如,Me.tbLastName)在代码中引用表单/报告控件
  • 避免创建与表/查询字段有问题的名字摆在首位
  • Me!当存在冲突时使用符号,例如与遗留应用程序(例如,Me!Pages
  • 包括隐藏的报表控件以访问报表 Recordset 字段值
  • .Value仅当情况需要增加冗长时才显式包含(例如,字典键)

¹什么是“数据可绑定”控件?
基本上,具有ControlSource属性的控件,例如 TextBox 或 ComboBox。不可绑定控件类似于 Label 或 CommandButton。TextBox 和 ComboBox 的默认属性都是.Value; 标签和命令按钮没有默认属性。