如何在 Access 2003 和 VBA 中使用控件集合

Posted

技术标签:

【中文标题】如何在 Access 2003 和 VBA 中使用控件集合【英文标题】:How to use Controls collection in Access 2003 and VBA 【发布时间】:2009-12-16 21:44:55 【问题描述】:

我正忙着想办法解决这个问题。

我想将一个 Controls 集合传递给一个函数,但我得到一个类型不匹配。这是函数声明:

Public Function DoStuffToCollection(topCtlList As Controls, isLocked As Boolean)

我是这样称呼它的:

Call DoStuffToCollection(myPage.Controls, isLocked)

myPage 是来自 TabControl 的 Page 控件。我已经逐步检查了代码,看看 myPage.Controls 中是否有某些内容并且确实存在。

为了好玩,我传入了 Me.Controls(这将是 Form 的控件集合)而不是 myPage.Controls,并且没有类型不匹配。表单控件集合和页面控件集合之间有区别吗?这真让我抓狂。

再深入一点,调试器调用 myPage.Controls Children/Children 作为类型,将 Me.Controls 作为 Controls/Controls 作为类型。为什么是这样?

[编辑] 只是添加一点信息。 doStuffToCollection 是一个递归函数,它应该锁定绑定字段并禁用选项卡控件中的任何类型的按钮。以前我只能在页面级别锁定它,但后来我添加了一个带有按钮的页面。该按钮没有被页面禁用。我知道这个http://allenbrowne.com/ser-56.html。不过,我还没有成功地让它适应我的需要。

【问题讨论】:

【参考方案1】:

@Tim Lentine 的建议非常好,在我看来直接回答了你的问题。

但我可能永远不会编写对表单的整个控件集合进行操作的子程序。原因是这样做实际上效率很低。但它是否合适取决于您步行该系列的频率和时间。

如果您在表单的 OnLoad 事件中执行过一次(您不希望在 OnOpen 中执行此操作,因为此时不能保证控件的数据绑定属性完全初始化 - 您不过,仍然可以对格式属性进行操作——但在 OnLoad 事件触发时一切都准备就绪),这没什么大不了的,将该集合传递给外部子例程是合适的。

但是,如果您为每条记录遍历它(例如隐藏/显示控件,或在未绑定的按表单查询界面中初始化条件字段),那么您将通过使用一个或多个显着提高表单的性能与任何普通表单的控件集合相比,自定义集合要循环的项目数量要少得多。然后,您可以重写 Tim 的代码以使用自定义集合,或者就此而言,仍然可以使用上面的 Object 变量并将其传递给自定义集合(并且仍然能够将其传递给表单的控件集合)。

基本上,您所做的是在表单的 OnLoad 事件中初始化集合。我通常会编写一个私有子例程来执行此操作,以便在发生代码重置时重新初始化:

  Private Sub SetupCollections()
    If mcolCriteria.Count = 0 Then
       Call PopulateCollections(Me, mcolCriteria, "Criteria")
    End If
  End Sub

  Public Sub PopulateCollections(frm As Form, pcol As Collection, strTag As String)
    Dim ctl As Control

    For Each ctl In frm.Controls
      If ctl.Tag = strTag Then
         pcol.Add ctl, ctl.Name
      End If
    Next ctl
    Set ctl = Nothing
  End Sub

在这种情况下,我确定将哪些控件添加到集合中的方法是设置这些控件的 Tag 属性。您还可以执行以下操作:

  Public Sub PopulateCollections(frm As Form, pcol As Collection, intControlType As AcControlType)
    Dim ctl As Control

    For Each ctl In frm.Controls
      If ctl.ControlType = intControlType
         pcol.Add ctl, ctl.Name
      End If
    Next ctl
    Set ctl = Nothing
  End Sub

要使用它,例如,您可以创建一个 Nullable 控件的集合,如下所示:

  If mcolControlsNullable.Count = 0 Then
     Call PopulateCollections(Me, mcolControlsNullable, acTextBox)
     Call PopulateCollections(Me, mcolControlsNullable, acComboBox)
     Call PopulateCollections(Me, mcolControlsNullable, acListBox)
   End If

对于布尔控件:

  If mcolControlsBoolean.Count = 0 Then
     Call PopulateCollections(Me, mcolControlsBoolean, acCheckBox)
  End If

对于具有默认值的选项组或其他控件:

  If mcolControlsWithDefaults.Count = 0 Then
     Call PopulateCollectionsWithDefaults(Me, mcolControlsWithDefaults, acTextBox)
     Call PopulateCollectionsWithDefaults(Me, mcolControlsWithDefaults, acComboBox)
     Call PopulateCollectionsWithDefaults(Me, mcolControlsWithDefaults, acListBox)
     Call PopulateCollectionsWithDefaults(Me, mcolControlsWithDefaults, acCheckBox)
     Call PopulateCollectionsWithDefaults(Me, mcolControlsWithDefaults, acOptionGroup)
  End If

  Public Sub PopulateCollectionsWithDefaults(frm As Form, pcol As Collection)
    Dim ctl As Control

    For Each ctl In frm.Controls
      If Len(ctl.DefaultValue) > 0 Then
         pcol.Add ctl, ctl.Name
      End If
    Next ctl
    Set ctl = Nothing
  End Sub

  Private Sub SetControlValuesFromDefaults(pcol As Collection)
    For Each ctl in pcol 
      ctl = ctl.DefaultValue
    Next ctl 
  End Sub

对于其他系列:

  Public Sub SetControlValues(pcol As Collection, varValue As Variant)
    For Each ctl in pcol
      ctl = varValue
    Next ctl
  End Sub

对于这组更复杂的集合,您需要这样的东西来初始填充它们:

  Private Sub SetupCollections()
    If mcolControlsNullable.Count = 0 Then
       Call PopulateCollections(Me, mcolControlsNullable, acTextBox)
       Call PopulateCollections(Me, mcolControlsNullable, acComboBox)
       Call PopulateCollections(Me, mcolControlsNullable, acListBox)
     End If
    If mcolControlsBoolean.Count = 0 Then
       Call PopulateCollections(Me, mcolControlsBoolean, acCheckBox)
    End If
    If mcolControlsWithDefaults.Count = 0 Then
       Call PopulateCollectionsWithDefaults(Me, mcolControlsWithDefaults, acTextBox)
       Call PopulateCollectionsWithDefaults(Me, mcolControlsWithDefaults, acComboBox)
       Call PopulateCollectionsWithDefaults(Me, mcolControlsWithDefaults, acListBox)
       Call PopulateCollectionsWithDefaults(Me, mcolControlsWithDefaults, acCheckBox)
       Call PopulateCollectionsWithDefaults(Me, mcolControlsWithDefaults, acOptionGroup)
    End If
  End Sub

...那么你会想要一个 sub 来初始化控件值:

  Private Sub InitializeControls()
    Call SetControlValues(mcolControlsNullable, Null)
    Call SetControlValues(mcolControlsBoolean, False)
    Call SetControlValuesFromDefaults(mcolControlsWithDefaults)
  End Sub

...这样您就可以在表单的 OnLoad 事件中设置所有内容:

  Call SetupCollections()
  Call InitializeControls()

当然,现在有一些不那么复杂的方法可以做到这一点。您可能希望初始化例程只遍历控件集合一次:

  Private Sub SetupCollections()
    Dim ctl As Control

    For Each ctl in Me.Controls
      If Len(ctl.DefaultValue) > 0 then
         mcolControlsWithDefaults.Add ctl, ctl.Name
      Else
         Select Case ctl.ControlType
           Case acTextBox, acComboBox, acListBox
             mcolControlsNullable.Add ctl, ctl.Name
           Case acCheckBox
             mcolControlsBoolean.Add ctl, ctl.Name
         End Select
      End If
    Next ctl
    Set ctl = Nothing
  End Sub

消除初始化例程的一种方法是使用自定义属性返回集合,使用内部静态变量,根据需要重新初始化。但是,这意味着要多次遍历控件集合,因此效率不高:

  Private Property Get colControlsNullable() As Collection
    Static colNullable As Collection

    If colNullable.Count = 0 Then
       Call PopulateCollections(Me, mcolControlsNullable, acTextBox)
       Call PopulateCollections(Me, mcolControlsNullable, acComboBox)
       Call PopulateCollections(Me, mcolControlsNullable, acListBox)       
    End If
    Set colControlsNullable = colNullable
  End Property

不幸的是,使用静态变量虽然很好地避免了模块级变量,但这意味着您的初始化例程效率较低,因为外部初始化例程无法利用这些静态变量通过控件来填充所有内容收藏。

所以,我不为这些集合使用自定义属性,尽管我希望可以。另一方面,如果我只有一个自定义控件集合,我会这样做。

不管怎样,我已经讨论了很久很久,而且卷积太多了,而且可能所有的空气代码都充满了错误......

【讨论】:

我走了一个不同的方向,但这可能是理想的方式。【参考方案2】:

您不能直接传递 Page 的控件集合似乎很奇怪。这可能与 Pages 集合本身是 special type of control collection 的事实有关。

另一种选择是将“topCtlList”参数声明为对象而不是控件。它使代码的可读性降低并且可能更容易出错,但它应该消除类型不匹配错误。

Public Function DoStuffToCollection(topCtlList As Object, isLocked As Boolean)
'Debug.Print TypeName(topCtlList)
Dim ctl As Control
For Each ctl In topCtlList
   Debug.Print ctl.Name
Next ctl

End Function

【讨论】:

不错!我当然不知道您可以将表单的控件集合作为对象传递。正如我的回答中所述,我倾向于不对表单的整个控件集合进行多次操作,而是使用自定义集合。这些当然可以作为参数传递给函数或子函数。 您有什么理由将代码示例声明为函数?它没有定义返回类型(这意味着它将返回一个变体——不知道它是空的还是 Null),并且您还没有设置返回值。为什么不将其定义为 Sub? 我会尝试对象路由。 @David是的,它可能应该是一个潜艇。但是 Function 和 Sub 之间是否存在性能差异? @David W. Fenton:他们也没有声明 topCtlList 和 isLocked ByVal,即草率的编码......这对于 IMO 上的两句话答案来说是公平的。 当然,布尔值应该是 ByVal,但是要使代码正常工作,必须通过 ByRef 传递 topCtlList。【参考方案3】:

另一种方法是使用“我”将表单作为表单对象传递。显然,我不确定到底有什么不同。

还戳了一下帮助中的Page Object。您可以传递一个 Page 对象。

【讨论】:

【参考方案4】:

我现在走的是另一条路。在我的选项卡控件中,我只有子表单。所以我从 OOP 开始,为每个子表单创建了一个名为 EnableForm 的函数。子表单现在可以处理它自己需要做的任何事情。在包含选项卡控件的表单中,我只是遍历选项卡控件的页面,查看页面是否包含子表单,然后调用 EnableForm 函数。

它很脏,但它有效,我将在代码中记录它。这个东西从一开始就缺少一些东西(以及这里的大多数其他访问数据库)。

【讨论】:

加载过多的子表单通常是不可取的。事实上,很多时候我不会在它所在的选项卡上加载子表单。 已采纳建议。如果我需要进行更多新的 Access 开发,我会牢记在心。我只想回到我通常安排的 C# 编程。

以上是关于如何在 Access 2003 和 VBA 中使用控件集合的主要内容,如果未能解决你的问题,请参考以下文章

Access 2003 FORMS:当我在运行时使用 VBA 设置时,即使我关闭然后打开,ListBox 的“RowSource”仍然存在

Access 2003 - VBA - 加入两种类型(字符串 = 日期)

如何执行 VBA Access 模块?

关于 MS Access 2003 和 VBA 编程

MS Access 2003 - 创建 MDE 文件失败:错误 VBA 已损坏?

从 Access 2010 VBA 打开 Excel 2010 文件