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

发布于 2024-09-03 09:32:02 字数 279 浏览 11 评论 0原文

在仔细阅读我正在记录的应用程序时,我遇到了一些问题访问对象属性/方法等时使用 bang 表示法的示例,在其他地方,他们使用点表示法来实现似乎相同的目的。

使用其中之一是否有区别或偏好?一些简单的谷歌搜索只能揭示有关该主题的有限信息,而有些人实际上在相反的情况下使用它。也许 MS 的某个编码标准部分表明了疯狂的方法?

While perusing an application that I'm documenting, I've run across some examples of bang notation in accessing object properties/methods, etc. and in other places they use dot notation for what seems like the same purpose.

Is there a difference or preference to using one or the other? Some simple googling only reveals limited information on the subject with some people actually using it in opposite cases. Perhaps there is a coding standards section from MS somewhere that indicates the method of madness?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

甜心小果奶 2024-09-10 09:32:02

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

它。该对象不必是集合。它不必具有名为 Item 的方法或属性。它所需要的只是一个可以接受字符串作为第一个参数的 Property GetFunction

有关更多详细信息和证明,请参阅我讨论此问题的博客文章:爆炸! VBA 中的(感叹号运算符)

Despite the (formerly) accepted answer to this question, the bang is not in fact a member or collection access operator. It does one simple and specific thing: The bang operator provides late-bound access to the default member of an object, by passing the literal name following the bang operator as a string argument to that default member.

That's it. The object doesn't have to be a collection. It doesn't have to have a method or property called Item. All it needs is a Property Get or Function which can accept a string as the first argument.

For much more detail and proof, see my blog post discussing this: The Bang! (Exclamation Operator) in VBA

人间不值得 2024-09-10 09:32:02

bang 运算符 (!) 是访问 Collection 或其他可枚举对象的成员的简写,例如 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"

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

  1. coll.Item("项目2")
    这是最明确的形式。

  2. coll("Item2")
    这是可行的,因为 ItemCollection 类的默认方法,因此您可以省略它。

  3. 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


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

The bang operator (!) is shorthand for accessing members of a Collection or other enumerable object, such as the Fields property of an ADODB.Recordset.

For example, you can create a Collection and add a few keyed items to it:

Dim coll As Collection
Set coll = New Collection

coll.Add "First Item", "Item1"
coll.Add "Second Item", "Item2"
coll.Add "Third  Item", "Item3"

You can access an item in this collection by its key in three ways:

  1. coll.Item("Item2")
    This is the most explicit form.

  2. coll("Item2")
    This works because Item is the default method of the Collection class, so you can omit it.

  3. coll!Item2
    This is short-hand for both of the above forms. At run-time, VB6 takes the text after the bang and passes it as a parameter to the Item method.

People seem to make this more complicated than it should be, which is why it's hard to find a straightforward explanation. Usually the complications or "reasons not to use the bang operator" stem from a misunderstanding of how simple it actually is. When someone has a problem with the bang operator, they tend to blame it instead of the real cause of the problem they are having, which is often more subtle.

For example, some people recommend not using the bang operator to access controls on a form. Thus, Me.txtPhone is preferred over Me!txtPhone. The "reason" this is seen as bad is that Me.txtPhone will be checked at compile-time for correctness, but Me!txtPhone won't.

In the first case, if you mistype the code as Me.txtFone and there is no control with that name, your code won't compile. In the second case, if you wrote Me!txtFone, you won't get a compile error. Instead, your code will blow up with a run-time error if it reaches the line of code that used Me!txtFone.

The problem with the argument against the bang operator is that this problem has nothing to do with the bang operator itself. It's behaving exactly the way it's supposed to.

When you add a control to a form, VB automatically adds a property to your form with the same name as the control you added. This property is part of the form's class, so the compiler can check for typos at compile-time if you access controls using the dot (".") operator (and you can access them using the dot operator precisely because VB created a named control property for you).

Since Me!ControlName is actually short-hand for Me.Controls("ControlName")1, it should not be suprising that you don't get any compile-time checks against mistyping the control name.

Put another way, if the bang operator is "bad" and the dot operator is "good", then you might think

Me.Controls("ControlName")

is better than

Me!ControlName

because the first version uses a dot, but in this case, the dot isn't any better at all, since you are still accessing the control name via a parameter. It's only "better" when there is an alternative way to write the code such that you will get compile-time checking. This happens to be the case with controls due to VB creating properties for each control for you, and this is why Me.ControlName is sometimes recommended over Me!ControlName.


  1. I had originally stated that the Controls property was the default property of the Form class, but David pointed out in the comments that Controls isn't the default property of Form. The actual default property returns a collection that includes the contents of Me.Controls, which is why the bang short-hand still works.
回忆追雨的时光 2024-09-10 09:32:02

几个陷阱可以作为已发布的两个特殊答案的附录:

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

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

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

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

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

.Value 怎么样?
尽管问题中没有明确提及,但这个话题在上面的评论中出现了。字段对象和大多数“可数据绑定”的控件对象的默认属性.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 字典中创建两个条目。相反,它会在最后一行抛出错误,因为我们正在尝试添加重复的键。也就是说,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 例程)不会添加新的字典项,因为是 Control 对象本身对字典进行索引,而不是 Control 的值。

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

  • 在表单/报表控件名称前添加控件类型指示符(例如,tbTextBoxlblLabel等)。
  • 请参阅使用 Me. 表示法的代码中的表单/报表控件(例如 Me.tbLastName
  • 避免使用 问题名称 首先
  • 出现冲突时使用 Me! 表示法,例如与旧版应用程序一样(例如,Me!Pages
  • 包含隐藏的报表控件以获取对报表 Recordset 字段值的访问权限
  • 仅当情况需要增加详细信息时才显式包含 .Value(例如,字典键)

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

Couple gotchas to serve as addenda to the two exceptional answers already posted:

Accessing recordset fields in forms vs. reports
The default item of Form objects in Access is a union of the form's Controls collection and the form recordset's Fields collection. If the name of a control conflicts with the name of a field, I'm not sure which object is actually returned. Since the default property of both a field and a control is their .Value, it's often a "distinction without a difference." In other words, one normally doesn't care which it is because the values of the field and control are often the same.

Beware of naming conflicts!
This situation is exacerbated by Access's Form and Report designer defaulting to naming bound controls the same as the recordset field to which they are bound. I've personally adopted the convention of renaming controls with their control type prefix (e.g., tbLastName for the text box bound to the LastName field).

Report recordset fields aren't there!
I said earlier the Form object's default item is a collection of Controls and Fields. However, the Report object's default item is only its collection of Controls. So if one wants to refer to a recordset field using the bang operator, one needs to include that field as the source for a (hidden, if desired) bound control.

Beware conflicts with explicit form/report properties
When one adds controls to a form or report, Access automatically creates properties that refer to these controls. For example, a control named tbLastName would be available from a form's code module by referring to Me.tbLastName. However, Access will not create such a property if it conflicts with an existing form or report property. For example, assume one adds a control named Pages. Referring to Me.Pages in the form's code module will return the form's Pages property, not the control named "Pages".

In this example, one could access the "Pages" control explicitly using Me.Controls("Pages") or implicitly using the bang operator, Me!Pages. Be aware, though, that using the bang operator means that Access might instead return a field named "Pages" if one exists in the form's recordset.

What about .Value?
Though not explicitly mentioned in the question, this topic came up in the above comments. The default property for Field objects and most "data-bindable"¹ Control objects is .Value. Since this is the default property, VBA will implicitly return the .Value property's value when it does not make sense to return the object itself. Thus, it's common practice to do this...

Dim EmployeeLastName As String
EmployeeLastName = Me.tbLastName

...instead of this...

EmployeeLastName = Me.tbLastName.Value

The above two statements produce identical results because EmployeeLastName is a string.

Beware the subtle .Value bug when keying dictionaries
There are some cases where this convention can cause subtle bugs. The most notable--and, if memory serves, only--one I've actually run into in practice is when using the value of a Field/Control as a Dictionary key.

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"

One would likely expect that the above code creates two entries in the EmployeePhoneNums dictionary. Instead, it throws an error on the last line because we are trying to add a duplicate key. That is, the tbLastName Control object itself is the key, not the value of the control. In this context, the control's value does not even matter.

In fact, I expect that the object's memory address (ObjPtr(Me.tbLastName)) is likely what's being used behind the scenes to index the dictionary. I did a quick test that seems to bear this out.

'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

When running the above code, exactly one dictionary item is added each time the form is closed and re-opened. Moving from record to record (and thus causing multiple calls to the Form_Current routine) does not add new dictionary items, because it is the Control object itself indexing the dictionary, and not the Control's value.

My personal recommendations/coding conventions
Over the years, I've adopted the following practices, YMMV:

  • Prefix Form/Report control names with control type indicators (e.g., tbTextBox, lblLabel, etc.)
  • Refer to Form/Report controls in code using Me. notation (e.g., Me.tbLastName)
  • Avoid creating table/query fields with problematic names in the first place
  • Use Me! notation when there are conflicts, such as with legacy applications (e.g., Me!Pages)
  • Include hidden report controls to gain access to report Recordset field values
  • Explicitly include .Value only when the situation warrants the added verbosity (e.g., Dictionary keys)

¹ What's a "data-bindable" control?
Basically, a control with a ControlSource property, such as a TextBox or ComboBox. A non-bindable control would be something like a Label or CommandButton. The default property of both a TextBox and ComboBox is .Value; Labels and CommandButtons have no default property.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文